diff --git a/packages/ui/src/main/constant.ts b/packages/ui/src/main/constant.ts new file mode 100644 index 0000000..57de845 --- /dev/null +++ b/packages/ui/src/main/constant.ts @@ -0,0 +1,4 @@ +import path from 'node:path' +import os from 'node:os' + +export const cacheDir = path.join(os.homedir(), '.geekgeekrun', 'cache') diff --git a/packages/ui/src/main/features/open-browser-download-window.ts b/packages/ui/src/main/features/open-browser-download-window.ts new file mode 100644 index 0000000..cb154bd --- /dev/null +++ b/packages/ui/src/main/features/open-browser-download-window.ts @@ -0,0 +1,28 @@ +import { ipcMain } from 'electron' +import { + createBrowserDownloadProgressWindow, + browserDownloadProgressWindow +} from '../window/browserDownloadProgressWindow' + +export async function openBrowserDownloadWindow({ windowOption } = {}) { + return new Promise((resolve, reject) => { + createBrowserDownloadProgressWindow({ ...windowOption }) + + let processDone = false + let pathOfDownloadedBrowser = null + function handler(_, executablePath) { + pathOfDownloadedBrowser = executablePath + processDone = true + browserDownloadProgressWindow.close() + } + ipcMain.once('browser-download-done', handler) + browserDownloadProgressWindow.once('closed', () => { + ipcMain.off('browser-download-done', handler) + if (processDone) { + resolve(pathOfDownloadedBrowser) + } else { + reject(new Error('USER_CANCELLED_CONFIG_BROWSER')) + } + }) + }) +} diff --git a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index.ts b/packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/index.ts similarity index 91% rename from packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index.ts rename to packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/index.ts index 11c24fc..7ff09ec 100644 --- a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index.ts +++ b/packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/index.ts @@ -9,7 +9,7 @@ export enum DOWNLOAD_ERROR_EXIT_CODE { DOWNLOAD_ERROR = 80 } -export const checkAndDownloadDependenciesForInit = async () => { +export const downloadDependenciesForInit = async () => { process.on('disconnect', () => app.exit()) process.on('uncaughtException', () => app.exit(DOWNLOAD_ERROR_EXIT_CODE.DOWNLOAD_ERROR)) app.dock?.hide() @@ -50,7 +50,7 @@ export const checkAndDownloadDependenciesForInit = async () => { timeoutTimer = setTimeout(() => { // will encounter this when network disconnected when downloading promiseWithResolver.reject(new Error('PROGRESS_NOT_CHANGED_TOO_LONG')) - }, 5 * 1000) + }, 10 * 1000) } else { clearTimeout(throttleProgressTimer) throttleProgressTimer = null @@ -73,7 +73,7 @@ export const checkAndDownloadDependenciesForInit = async () => { ) throttleProgressTimer = setTimeout(() => { throttleProgressTimer = null - }, 2500) + }, 500) } } }) @@ -94,11 +94,13 @@ export const checkAndDownloadDependenciesForInit = async () => { pipe, JSON.stringify({ type: 'PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR', - ...err instanceof Error ? { - name: err.name, - message: err.message, - stack: err.stack - } : null + ...(err instanceof Error + ? { + name: err.name, + message: err.message, + stack: err.stack + } + : null) }) + '\r\n' ) await sleep(1000) diff --git a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/browser-history.ts b/packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/utils/browser-history.ts similarity index 100% rename from packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/browser-history.ts rename to packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/utils/browser-history.ts diff --git a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable/index.ts b/packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable/index.ts similarity index 97% rename from packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable/index.ts rename to packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable/index.ts index 9f823b0..90f62df 100644 --- a/packages/ui/src/main/flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable/index.ts +++ b/packages/ui/src/main/flow/DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable/index.ts @@ -1,5 +1,3 @@ -import * as path from 'node:path' -import * as os from 'node:os' import * as fs from 'node:fs' import type { InstalledBrowser } from '@puppeteer/browsers' import { @@ -10,6 +8,7 @@ import { } from '../browser-history' import gtag from '../../../../utils/gtag' import { EXPECT_CHROMIUM_BUILD_ID } from '../../../../../common/constant' +import { cacheDir } from '../../../../constant' const getPuppeteerManagerModule = async () => { const puppeteerManager = await import('@puppeteer/browsers') @@ -17,7 +16,6 @@ const getPuppeteerManagerModule = async () => { return puppeteerManager } -const cacheDir = path.join(os.homedir(), '.geekgeekrun', 'cache') const getExpectCachedPuppeteerExecutable = async (): Promise => { const puppeteerManager = await getPuppeteerManagerModule() 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 e6d4924..0518029 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 @@ -6,7 +6,7 @@ import { getPublicDbFilePath } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs' // import { pipeWriteRegardlessError } from '../utils/pipe' -import { getAnyAvailablePuppeteerExecutable } from '../CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable' +import { getAnyAvailablePuppeteerExecutable } from '../DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable' import { sleep } from '@geekgeekrun/utils/sleep.mjs' import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../../common/enums/auto-start-chat' import attachListenerForKillSelfOnParentExited from '../../utils/attachListenerForKillSelfOnParentExited' diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts index 2044f43..6ecc2dc 100644 --- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts @@ -12,7 +12,7 @@ import { import { ChildProcess } from 'child_process' import * as JSONStream from 'JSONStream' import { checkCookieListFormat } from '../../../../common/utils/cookie' -import { getAnyAvailablePuppeteerExecutable } from '../../../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable/index' +import { getAnyAvailablePuppeteerExecutable } from '../../DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable/index' import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../../../common/enums/auto-start-chat' import { mainWindow } from '../../../window/mainWindow' import { diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts index 79bc81c..03a3b91 100644 --- a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts +++ b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts @@ -34,7 +34,7 @@ import cheerio from 'cheerio' import { connectToDaemon, sendToDaemon } from '../OPEN_SETTING_WINDOW/connect-to-daemon' // import { pushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS } from '../../utils/screenshot' import { checkShouldExit } from '../../utils/worker' -import { getAnyAvailablePuppeteerExecutable } from '../CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable' +import { getAnyAvailablePuppeteerExecutable } from '../DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable' import minimist from 'minimist' import { checkCookieListFormat } from '../../../common/utils/cookie' import { loginWithCookieAssistant } from '../../features/login-with-cookie-assistant' diff --git a/packages/ui/src/main/index.ts b/packages/ui/src/main/index.ts index fd787bd..dcf690f 100644 --- a/packages/ui/src/main/index.ts +++ b/packages/ui/src/main/index.ts @@ -1,7 +1,7 @@ import minimist from 'minimist' -import { runCommon } from './features/run-common'; -import { launchDaemon } from './flow/OPEN_SETTING_WINDOW/launch-daemon'; -import { app } from 'electron'; +import { runCommon } from './features/run-common' +import { launchDaemon } from './flow/OPEN_SETTING_WINDOW/launch-daemon' +import { app } from 'electron' // 捕获未处理的 EPIPE 错误 process.on('uncaughtException', (err) => { @@ -9,13 +9,13 @@ process.on('uncaughtException', (err) => { return } throw err -}); +}) const isUiDev = process.env.NODE_ENV === 'development' const commandlineArgs = minimist(isUiDev ? process.argv.slice(2) : process.argv.slice(1)) console.log(commandlineArgs) -const runMode = commandlineArgs['mode']; +const runMode = commandlineArgs['mode'] ;(async () => { switch (runMode) { @@ -27,11 +27,9 @@ const runMode = commandlineArgs['mode']; waitForProcessHandShakeAndRunAutoChat() break } - case 'checkAndDownloadDependenciesForInit': { - const { checkAndDownloadDependenciesForInit } = await import( - './flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index' - ) - checkAndDownloadDependenciesForInit() + case 'downloadDependenciesForInit': { + const { downloadDependenciesForInit } = await import('./flow/DOWNLOAD_DEPENDENCIES/index') + downloadDependenciesForInit() break } case 'launchBossZhipinLoginPageWithPreloadExtension': { diff --git a/packages/ui/src/main/window/browserAssistantWindow.ts b/packages/ui/src/main/window/browserAssistantWindow.ts index ae89825..73bc8be 100644 --- a/packages/ui/src/main/window/browserAssistantWindow.ts +++ b/packages/ui/src/main/window/browserAssistantWindow.ts @@ -1,13 +1,11 @@ -import { ChildProcess } from 'child_process' import { BrowserWindow, ipcMain } from 'electron' import path from 'path' -import { getAnyAvailablePuppeteerExecutable } from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable' -import * as childProcess from 'node:child_process' -import * as JSONStream from 'JSONStream' +import { getAnyAvailablePuppeteerExecutable } from '../flow/DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable' import { getLastUsedAndAvailableBrowser, saveLastUsedAndAvailableBrowserInfo -} from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/browser-history' +} from '../flow/DOWNLOAD_DEPENDENCIES/utils/browser-history' +import { openBrowserDownloadWindow } from '../features/open-browser-download-window' export let browserAssistantWindow: BrowserWindow | null = null @@ -83,67 +81,15 @@ export function createBrowserAssistantWindow( } ) - let subProcessOfCheckAndDownloadDependencies: ChildProcess | null = null - registerHandleWithWindow(browserAssistantWindow, 'setup-dependencies', async () => { - if (subProcessOfCheckAndDownloadDependencies) { - return - } - subProcessOfCheckAndDownloadDependencies = childProcess.spawn( - process.argv[0], - [process.argv[1], `--mode=checkAndDownloadDependenciesForInit`], - { - stdio: [null, null, null, 'pipe', 'ipc'] + registerHandleWithWindow(browserAssistantWindow, 'download-browser-with-downloader', async () => { + return await openBrowserDownloadWindow({ + windowOption: { + parent: browserAssistantWindow!, + modal: true, + show: true } - ) - return new Promise((resolve, reject) => { - subProcessOfCheckAndDownloadDependencies!.stdio[3]!.pipe(JSONStream.parse()).on( - 'data', - (raw) => { - const data = raw - switch (data.type) { - case 'NEED_RESETUP_DEPENDENCIES': - case 'PUPPETEER_DOWNLOAD_PROGRESS': { - browserAssistantWindow?.webContents.send(data.type, data) - break - } - case 'PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR': { - console.error(data) - break - } - default: { - return - } - } - } - ) - subProcessOfCheckAndDownloadDependencies!.once('exit', (exitCode) => { - switch (exitCode) { - case 0: { - resolve(exitCode) - break - } - default: { - reject('PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR') - break - } - } - subProcessOfCheckAndDownloadDependencies = null - }) }) }) - const killHandler = async () => { - try { - subProcessOfCheckAndDownloadDependencies?.kill() - } catch { - // - } finally { - subProcessOfCheckAndDownloadDependencies = null - } - } - browserAssistantWindow.once('closed', () => { - killHandler() - }) - return browserAssistantWindow! } diff --git a/packages/ui/src/main/window/browserDownloadProgressWindow.ts b/packages/ui/src/main/window/browserDownloadProgressWindow.ts new file mode 100644 index 0000000..e915568 --- /dev/null +++ b/packages/ui/src/main/window/browserDownloadProgressWindow.ts @@ -0,0 +1,124 @@ +import { ChildProcess } from 'child_process' +import { BrowserWindow, ipcMain } from 'electron' +import path from 'path' +import * as childProcess from 'node:child_process' +import * as JSONStream from 'JSONStream' +import * as fs from 'node:fs' +import { cacheDir } from '../constant' +import { EXPECT_CHROMIUM_BUILD_ID } from '../../common/constant' +import * as puppeteerManager from '@puppeteer/browsers' + +export let browserDownloadProgressWindow: BrowserWindow | null = null + +const registerHandleWithWindow = ( + win: BrowserWindow, + ...args: Parameters +) => { + const [channel, handler] = args + ipcMain.handle(channel, handler) + win.once('closed', () => ipcMain.removeHandler(channel)) +} + +export function createBrowserDownloadProgressWindow( + opt?: Electron.BrowserWindowConstructorOptions +): BrowserWindow { + // Create the browser window. + if (browserDownloadProgressWindow) { + browserDownloadProgressWindow!.close() + } + browserDownloadProgressWindow = new BrowserWindow({ + width: 600, + height: 200, + resizable: false, + show: false, + autoHideMenuBar: true, + webPreferences: { + preload: path.join(__dirname, '../preload/index.js'), + sandbox: false + }, + ...opt + }) + + browserDownloadProgressWindow.on('ready-to-show', () => { + browserDownloadProgressWindow!.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']) { + browserDownloadProgressWindow.loadURL( + process.env['ELECTRON_RENDERER_URL'] + '#/browserDownloadProgress' + ) + } else { + browserDownloadProgressWindow.loadURL( + 'file://' + path.join(__dirname, '../renderer/index.html') + '#/browserDownloadProgress' + ) + } + + let subProcessOfCheckAndDownloadDependencies: ChildProcess | null = null + registerHandleWithWindow(browserDownloadProgressWindow, 'setup-dependencies', async () => { + if (subProcessOfCheckAndDownloadDependencies) { + return + } + subProcessOfCheckAndDownloadDependencies = childProcess.spawn( + process.argv[0], + [process.argv[1], `--mode=downloadDependenciesForInit`], + { + stdio: [null, null, null, 'pipe', 'ipc'] + } + ) + return new Promise((resolve, reject) => { + subProcessOfCheckAndDownloadDependencies!.stdio[3]!.pipe(JSONStream.parse()).on( + 'data', + (raw) => { + const data = raw + switch (data.type) { + case 'NEED_RESETUP_DEPENDENCIES': + case 'PUPPETEER_DOWNLOAD_PROGRESS': { + browserDownloadProgressWindow?.webContents.send(data.type, data) + break + } + case 'PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR': { + console.error(data) + break + } + default: { + return + } + } + } + ) + subProcessOfCheckAndDownloadDependencies!.once('exit', (exitCode) => { + const executablePath = puppeteerManager.computeExecutablePath({ + browser: puppeteerManager.Browser.CHROME, + cacheDir, + buildId: EXPECT_CHROMIUM_BUILD_ID + }) + if (exitCode === 0 && fs.existsSync(executablePath)) { + resolve(executablePath) + } else { + reject('PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR') + } + subProcessOfCheckAndDownloadDependencies = null + }) + }) + }) + + const killHandler = async () => { + try { + subProcessOfCheckAndDownloadDependencies?.kill() + } catch { + // + } finally { + subProcessOfCheckAndDownloadDependencies = null + } + } + browserDownloadProgressWindow.once('closed', () => { + killHandler() + }) + browserDownloadProgressWindow.once('closed', () => { + browserDownloadProgressWindow = null + }) + + return browserDownloadProgressWindow! +} diff --git a/packages/ui/src/main/window/cookieAssistantWindow.ts b/packages/ui/src/main/window/cookieAssistantWindow.ts index dc927b7..705584d 100644 --- a/packages/ui/src/main/window/cookieAssistantWindow.ts +++ b/packages/ui/src/main/window/cookieAssistantWindow.ts @@ -1,7 +1,7 @@ import { ChildProcess } from 'child_process' import { BrowserWindow, ipcMain } from 'electron' import path from 'path' -import { getAnyAvailablePuppeteerExecutable } from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable' +import { getAnyAvailablePuppeteerExecutable } from '../flow/DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable' import * as childProcess from 'node:child_process' import * as JSONStream from 'JSONStream' diff --git a/packages/ui/src/renderer/src/page/BrowserAssistant/index.vue b/packages/ui/src/renderer/src/page/BrowserAssistant/index.vue index d07399d..4a88db3 100644 --- a/packages/ui/src/renderer/src/page/BrowserAssistant/index.vue +++ b/packages/ui/src/renderer/src/page/BrowserAssistant/index.vue @@ -32,11 +32,11 @@
  • 方案一:通过本程序下载 Google Chrome for Testing {{ EXPECT_CHROMIUM_BUILD_ID }} - - 点击此处即可下载;这个浏览器由本程序独占,不会影响到当前的 Google Chrome + 点击此处即可下载;这个浏览器仅供本程序使用,不会影响到当前 Google Chrome 安装。本程序开发过程中主要是使用这个浏览器测试的,可以保证兼容性。但网络波动,有一定概率下载失败。如多次尝试后确实不能下载成功,请尝试方案二。。网络波动,有一定概率下载失败;如多次尝试后确实不能下载成功,请尝试方案二。(推荐) @@ -54,7 +54,7 @@ >浏览器升级后某些功能不兼容导致本程序不能正确运行的问题。您可以提交 Issue来反馈新版本浏览器不能正常运行的问题,同时请再尝试方法一。 + >来反馈新版本浏览器不能正常运行的问题,同时请再尝试方案一。
  • @@ -142,13 +142,19 @@ async function autoDetectPuppeteerExecutable() { noSave: true }) if (!result) { - ElMessage.warning({ + ElMessage({ message: '未检测到可用浏览器的可执行文件', - type: 'warning' + type: 'warning', + grouping: true }) return } formData.value.browserPath = result.executablePath + ElMessage({ + message: '已找到可用浏览器,可执行文件路径已填入输入框', + type: 'success', + grouping: true + }) } async function browserExecutableFile() { @@ -177,7 +183,9 @@ function handleCancel() { gtagRenderer('cancel_clicked') window.close() } +const formRef = ref() async function handleSave() { + await formRef.value.validate() await ipcRenderer.invoke('save-last-used-and-available-browser-info', { executablePath: formData.value.browserPath, browser: '' @@ -188,6 +196,18 @@ const handleFeedbackClick = () => { gtagRenderer('goto_feedback_for_ba_clicked') electron.ipcRenderer.send('send-feed-back-to-github-issue') } +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 + }) + } +} diff --git a/packages/ui/src/renderer/src/page/BrowserDownloadProgress/index.vue b/packages/ui/src/renderer/src/page/BrowserDownloadProgress/index.vue new file mode 100644 index 0000000..14058bd --- /dev/null +++ b/packages/ui/src/renderer/src/page/BrowserDownloadProgress/index.vue @@ -0,0 +1,92 @@ + + + diff --git a/packages/ui/src/renderer/src/router/index.ts b/packages/ui/src/renderer/src/router/index.ts index c7dcaa1..0d6e606 100644 --- a/packages/ui/src/renderer/src/router/index.ts +++ b/packages/ui/src/renderer/src/router/index.ts @@ -22,16 +22,14 @@ const routes: Array = [ component: () => import('@renderer/page/BrowserAssistant/index.vue'), meta: { title: '浏览器助手' - }, - children: [ - { - path: '/downloadingDependencies', - component: () => import('@renderer/page/BrowserAssistant/page/DownloadingDependencies.vue'), - meta: { - title: '正在下载核心组件' - } - } - ] + } + }, + { + path: '/browserDownloadProgress', + component: () => import('@renderer/page/BrowserDownloadProgress/index.vue'), + meta: { + title: '正在下载浏览器' + } }, { path: '/llmConfig',