From 860847963bcdf1356b7765d080f9706c98c60865 Mon Sep 17 00:00:00 2001 From: geekgeekrun Date: Thu, 15 Jan 2026 12:46:16 +0800 Subject: [PATCH] add screenshot show --- .../geek-auto-start-chat-with-boss/index.mjs | 3 +- packages/pm/daemon.js | 39 +++++++------ .../main.mjs | 3 +- .../index.ts | 5 +- .../flow/READ_NO_REPLY_AUTO_REMINDER/index.ts | 11 ++++ packages/ui/src/main/utils/screenshot.ts | 55 +++++++++++++++++++ .../src/features/TaskManager/Item.vue | 27 +++++++-- 7 files changed, 119 insertions(+), 24 deletions(-) create mode 100644 packages/ui/src/main/utils/screenshot.ts diff --git a/packages/geek-auto-start-chat-with-boss/index.mjs b/packages/geek-auto-start-chat-with-boss/index.mjs index 193965b..04388c1 100644 --- a/packages/geek-auto-start-chat-with-boss/index.mjs +++ b/packages/geek-auto-start-chat-with-boss/index.mjs @@ -1468,8 +1468,9 @@ export async function mainLoop (hooks) { height: 900 - 140, } }) - hooks.puppeteerLaunched?.call() + hooks.puppeteerLaunched?.call(browser) page = (await browser.pages())[0] + hooks.pageGotten?.call(page) //set cookies hooks.cookieWillSet?.call(bossCookies) for(let i = 0; i < bossCookies.length; i++){ diff --git a/packages/pm/daemon.js b/packages/pm/daemon.js index 2278bd7..909a022 100644 --- a/packages/pm/daemon.js +++ b/packages/pm/daemon.js @@ -30,7 +30,7 @@ const path = require('path'); const split2 = require('split2'); const PORT = 12345; -const workers = new Map(); // workerId -> { process, status, restartCount, socket } +const workers = new Map(); // workerId -> { process, status, restartCount, socket, latestScreenshot, latestScreenshotAt } const guiClients = new Set(); // GUI客户端连接集合 const stoppedWorkers = new Set(); // 被用户主动停止的workerId集合,用于防止竞态条件 const pidToProcessInfoMap = new Map() @@ -148,12 +148,11 @@ function handleMessage(socket, message) { return; } - // 工具进程发送的消息(数据、心跳等) - if (message.type === 'worker-message' || message.type === 'worker-heartbeat' || message.type === 'worker-data') { - const workerId = message.workerId; - const workerInfo = workers.get(workerId); - - if (workerInfo && workerInfo.socket === socket) { + // 工具进程发送的消息(数据、心跳、截图等) + const workerId = message.workerId; + const workerInfo = workers.get(workerId); + switch (message.type) { + case 'worker-message': { // 转发工具进程消息到GUI客户端 broadcastToGUI({ type: 'worker-message', @@ -161,15 +160,20 @@ function handleMessage(socket, message) { data: message.data || message, timestamp: Date.now() }); - - // // 如果是心跳,更新最后心跳时间 - // if (message.type === 'worker-heartbeat') { - // workerInfo.lastHeartbeat = Date.now(); - // } - } else { - sendResponse(socket, _callbackUuid, { error: '未注册的工具进程连接' }); + break + } + case 'worker-screenshot': { + if (workerInfo && message.data && message.data.screenshot /* && workerInfo.socket === socket */) { + // 如果携带截图信息,则在守护进程内缓存一份,供 get-status 使用 + try { + workerInfo.latestScreenshot = message.data.screenshot; + workerInfo.latestScreenshotAt = Date.now(); + } catch (e) { + console.error('缓存 worker 截图信息失败:', e); + } + } + break } - return; } // GUI客户端的控制消息 @@ -393,7 +397,10 @@ function getWorkersStatus() { // lastHeartbeat: workerInfo.lastHeartbeat, command: workerInfo.command, args: workerInfo.args, - pid: workerInfo.process?.pid + pid: workerInfo.process?.pid, + // 最新截图(通常是 data URL 或 base64 字符串),以及截图时间 + screenshot: workerInfo.latestScreenshot ?? null, + screenshotAt: workerInfo.latestScreenshotAt ?? null, }); } return status; diff --git a/packages/run-core-of-geek-auto-start-chat-with-boss/main.mjs b/packages/run-core-of-geek-auto-start-chat-with-boss/main.mjs index 8b75b90..6f19d1b 100644 --- a/packages/run-core-of-geek-auto-start-chat-with-boss/main.mjs +++ b/packages/run-core-of-geek-auto-start-chat-with-boss/main.mjs @@ -47,7 +47,8 @@ const main = async () => { } const hooks = { daemonInitialized: new AsyncSeriesHook(), - puppeteerLaunched: new SyncHook(), + puppeteerLaunched: new SyncHook(['browser']), + pageGotten: new SyncHook(['page']), pageLoaded: new SyncHook(), cookieWillSet: new SyncHook(['cookies']), userInfoResponse: new AsyncSeriesHook(['userInfo']), diff --git a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index.ts b/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index.ts index 6c23ae7..933ae91 100644 --- a/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index.ts +++ b/packages/ui/src/main/flow/GEEK_AUTO_START_CHAT_WITH_BOSS_MAIN/index.ts @@ -17,6 +17,7 @@ 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' const { default: SqlitePlugin } = SqlitePluginModule const rerunInterval = (() => { @@ -34,6 +35,7 @@ const initPlugins = (hooks) => { new DingtalkPlugin(dingTalkAccessToken).apply(hooks) new SqlitePlugin(getPublicDbFilePath()).apply(hooks) new GtagPlugin().apply(hooks) + new PeriodPushCurrentPageScreenshotPlugin().apply(hooks) } async function checkShouldExit () { @@ -91,7 +93,8 @@ const runAutoChat = async () => { } const hooks = { - puppeteerLaunched: new SyncHook(), + puppeteerLaunched: new SyncHook(['browser']), + pageGotten: new SyncHook(['page']), pageLoaded: new SyncHook(), cookieWillSet: new SyncHook(['cookies']), userInfoResponse: new AsyncSeriesHook(['userInfo']), diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/index.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/index.ts index fe6245d..c5f2707 100644 --- a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/index.ts +++ b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/index.ts @@ -25,6 +25,7 @@ import { JobHireStatus } from '@geekgeekrun/sqlite-plugin/dist/enums'; import dayjs from 'dayjs' import cheerio from 'cheerio' import { connectToDaemon, sendToDaemon } from '../OPEN_SETTING_WINDOW/connect-to-daemon' +import { pushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS } from '../../utils/screenshot' const throttleIntervalMinutes = readConfigFile('boss.json').autoReminder?.throttleIntervalMinutes ?? 10 @@ -48,6 +49,16 @@ export const pageMapByName: { boss?: Page | null } = {} +async function periodPushCurrentPageScreenshot () { + try { + await pushCurrentPageScreenshot(pageMapByName.boss) + setTimeout(periodPushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS) + } + catch {} +} + +periodPushCurrentPageScreenshot() + async function saveCurrentChatRecord(page) { const userInfo = await page.evaluate( 'document.querySelector(".main-wrap").__vue__.$store.state.userInfo' diff --git a/packages/ui/src/main/utils/screenshot.ts b/packages/ui/src/main/utils/screenshot.ts new file mode 100644 index 0000000..f8b0a2a --- /dev/null +++ b/packages/ui/src/main/utils/screenshot.ts @@ -0,0 +1,55 @@ +import { sendToDaemon } from "../flow/OPEN_SETTING_WINDOW/connect-to-daemon" + +export const SCREENSHOT_INTERVAL_MS = 2500 + +export async function pushCurrentPageScreenshot (page) { + try { + if (!page) { + return + } + // 尝试截图当前页面(压缩为 jpeg + base64,避免文件写盘) + const screenshotBase64 = await page.screenshot({ + type: 'jpeg', + quality: 60, + encoding: 'base64', + fullPage: false + }) + const screenshotAt = Date.now() + await sendToDaemon({ + type: 'worker-screenshot', + workerId: process.env.GEEKGEEKRUND_WORKER_ID, + data: { + screenshot: `data:image/jpeg;base64,${screenshotBase64}`, + screenshotAt, + pageUrl: page.url?.() ?? null + } + }) + } catch (err) { + // 截图失败不应影响主流程 + console.warn('[READ_NO_REPLY_AUTO_REMINDER] pushCurrentPageScreenshot error', err) + } +} + +export class PeriodPushCurrentPageScreenshotPlugin { + apply(hooks) { + hooks.pageGotten.tap( + 'PeriodPushCurrentPageScreenshotPlugin', + (page) => { + async function periodPushCurrentPageScreenshot () { + try { + if (!page) { + return + } + if (page.isClosed()) { + return + } + await pushCurrentPageScreenshot(page) + setTimeout(periodPushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS) + } + catch {} + } + periodPushCurrentPageScreenshot() + } + ) + } +} \ No newline at end of file diff --git a/packages/ui/src/renderer/src/features/TaskManager/Item.vue b/packages/ui/src/renderer/src/features/TaskManager/Item.vue index 9f0650e..21355bb 100644 --- a/packages/ui/src/renderer/src/features/TaskManager/Item.vue +++ b/packages/ui/src/renderer/src/features/TaskManager/Item.vue @@ -1,9 +1,20 @@