From 3d148e8e01c7939ac3b0ef5116a14f7abd972eee Mon Sep 17 00:00:00 2001 From: geekgeekrun Date: Tue, 22 Apr 2025 03:20:37 +0800 Subject: [PATCH] add basic llm testing window --- .../flow/OPEN_SETTING_WINDOW/ipc/index.ts | 37 +++- .../boss-operation.ts | 26 ++- .../readNoReplyReminderLlmMockWindow.ts | 47 +++++ .../page/MainLayout/ReadNoReplyReminder.vue | 196 ++++++++++-------- .../page/ReadNoReplyReminderLlmMock/index.vue | 101 +++++++++ packages/ui/src/renderer/src/router/index.ts | 7 + 6 files changed, 319 insertions(+), 95 deletions(-) create mode 100644 packages/ui/src/main/window/readNoReplyReminderLlmMockWindow.ts create mode 100644 packages/ui/src/renderer/src/page/ReadNoReplyReminderLlmMock/index.vue 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 a20f520..ebd53dd 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 @@ -33,7 +33,10 @@ import { WriteStream } from 'node:fs' import { hasOwn } from '@vue/shared' import { createLlmConfigWindow, llmConfigWindow } from '../../../window/llmConfigWindow' import { createResumeEditorWindow, resumeEditorWindow } from '../../../window/resumeEditorWindow' -import { getValidTemplate } from '../../READ_NO_REPLY_AUTO_REMINDER/boss-operation' +import { + getValidTemplate, + requestNewMessageContent +} from '../../READ_NO_REPLY_AUTO_REMINDER/boss-operation' import { autoReminderPromptTemplateFileName, writeDefaultAutoRemindPrompt @@ -42,6 +45,11 @@ import { checkIsResumeContentValid, resumeContentEnoughDetect } from '../../../../common/utils/resume' +import { + createReadNoReplyReminderLlmMockWindow, + readNoReplyReminderLlmMockWindow +} from '../../../window/readNoReplyReminderLlmMockWindow' +import { RequestSceneEnum } from '../../../features/llm-request-log' export default function initIpc() { ipcMain.handle('fetch-config-file-content', async () => { @@ -539,6 +547,33 @@ export default function initIpc() { } } }) + ipcMain.on('test-llm-config-effect', () => { + createReadNoReplyReminderLlmMockWindow({ + parent: mainWindow!, + modal: true, + show: true + }) + async function requestLlm(_, requestPayload) { + return await requestNewMessageContent(requestPayload.messageList, { + requestScene: RequestSceneEnum.testing + }) + } + ipcMain.handle('request-llm-for-test', requestLlm) + readNoReplyReminderLlmMockWindow?.once('closed', () => { + ipcMain.removeHandler('request-llm-for-test') + }) + async function getLlmConfigList() { + return await readConfigFile('llm.json') + } + ipcMain.handle('get-llm-config-for-test', getLlmConfigList) + readNoReplyReminderLlmMockWindow?.once('closed', () => { + ipcMain.removeHandler('get-llm-config-for-test') + }) + }) + ipcMain.on('close-read-no-reply-reminder-llm-mock-window', () => + readNoReplyReminderLlmMockWindow?.close() + ) + ipcMain.handle('exit-app-immediately', () => { app.exit(0) }) diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/boss-operation.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/boss-operation.ts index 0d61ad5..992558c 100644 --- a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/boss-operation.ts +++ b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER/boss-operation.ts @@ -115,7 +115,7 @@ export const writeDefaultAutoRemindPrompt = async () => { await writeStorageFile(autoReminderPromptTemplateFileName, defaultPrompt, { isJson: false }) } -export const sendGptContent = async (page: Page, chatRecords) => { +export const requestNewMessageContent = async (chatRecords, { requestScene } = {}) => { const template = await getValidTemplate() const resumeObject = (await readConfigFile('resumes.json'))?.[0] const resumeContent = formatResumeJsonToMarkdown(resumeObject) @@ -148,6 +148,9 @@ export const sendGptContent = async (page: Page, chatRecords) => { } console.log(chatList) let res, llmConfig + const llmRequestRecord: Omit & { + providerApiSecret: string + } = {} while (!res) { const llmConfigList = await readConfigFile('llm.json') llmConfig = pickLlmConfigFromList(llmConfigList) @@ -155,17 +158,15 @@ export const sendGptContent = async (page: Page, chatRecords) => { throw new Error(`CANNOT_FIND_A_USABLE_MODEL`) } console.log(llmConfig.providerCompleteApiUrl) - const llmRequestRecord: Omit & { - providerApiSecret: string - } = { + Object.assign(llmRequestRecord, { providerCompleteApiUrl: llmConfig.providerCompleteApiUrl, model: llmConfig.model, providerApiSecret: llmConfig.providerApiSecret, requestStartTime: new Date(), hasError: false, errorMessage: '', - requestScene: RequestSceneEnum.readNoReplyAutoReminder - } + requestScene + }) try { const completion = await completes( { @@ -227,6 +228,19 @@ export const sendGptContent = async (page: Page, chatRecords) => { } catch (err) { throw new Error(`fail to parse response. ${err?.message} ${res?.message?.content}`) } + return { + responseText: textToSend, + usedLlmConfig: llmConfig, + recordInfo: llmRequestRecord + } +} + +export async function sendGptContent(page: Page, chatRecords) { + const textToSend = ( + await requestNewMessageContent(chatRecords, { + requestScene: RequestSceneEnum.readNoReplyAutoReminder + }) + ).responseText const chatInputSelector = `.chat-conversation .message-controls .chat-input` const chatInputHandle = await page.$(chatInputSelector) await chatInputHandle.click() diff --git a/packages/ui/src/main/window/readNoReplyReminderLlmMockWindow.ts b/packages/ui/src/main/window/readNoReplyReminderLlmMockWindow.ts new file mode 100644 index 0000000..a90d9f1 --- /dev/null +++ b/packages/ui/src/main/window/readNoReplyReminderLlmMockWindow.ts @@ -0,0 +1,47 @@ +import { BrowserWindow } from 'electron' +import path from 'path' + +export let readNoReplyReminderLlmMockWindow: BrowserWindow | null = null +export function createReadNoReplyReminderLlmMockWindow( + opt?: Electron.BrowserWindowConstructorOptions +): BrowserWindow { + // Create the browser window. + if (readNoReplyReminderLlmMockWindow) { + readNoReplyReminderLlmMockWindow!.show() + } + readNoReplyReminderLlmMockWindow = new BrowserWindow({ + width: 600, + height: 800, + resizable: false, + show: false, + autoHideMenuBar: true, + frame: false, + webPreferences: { + preload: path.join(__dirname, '../preload/index.js'), + sandbox: false + }, + ...opt + }) + + readNoReplyReminderLlmMockWindow.on('ready-to-show', () => { + readNoReplyReminderLlmMockWindow!.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']) { + readNoReplyReminderLlmMockWindow.loadURL( + process.env['ELECTRON_RENDERER_URL'] + '#/readNoReplyReminderLlmMock' + ) + } else { + readNoReplyReminderLlmMockWindow.loadURL( + 'file://' + path.join(__dirname, '../renderer/index.html') + '#/readNoReplyReminderLlmMock' + ) + } + + readNoReplyReminderLlmMockWindow!.once('closed', () => { + readNoReplyReminderLlmMockWindow = null + }) + + return readNoReplyReminderLlmMockWindow! +} diff --git a/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue b/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue index 71bb665..cc0cd0f 100644 --- a/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue +++ b/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue @@ -81,7 +81,7 @@
- 使用外部编辑器编辑提示词模板 + 使用外部编辑器编辑提示词模板 (Markdown)
+ + 使用当前配置模拟已读不回复聊过程 +
@@ -256,6 +261,100 @@ watch( } ) +async function checkIsCanRun() { + if (!(await electron.ipcRenderer.invoke('check-is-resume-content-valid'))) { + gtagRenderer('cannot_launch_for_invalid_rc_dialog_show') + try { + await ElMessageBox.confirm(`简历内容无效;您需要编辑一下您的简历`, { + cancelButtonText: '取消', + confirmButtonText: '好的,去编辑我的简历', + dangerouslyUseHTMLString: true + }) + gtagRenderer('invalid_rc_dialog_click_confirm') + try { + await electron.ipcRenderer.invoke('resume-edit') + } catch (err) { + console.log(err) + } + } catch { + gtagRenderer('invalid_rc_dialog_click_cancel') + } + return false + } + try { + await electron.ipcRenderer.invoke('check-if-llm-config-list-valid') + } catch (err) { + if (err?.message?.includes(`CANNOT_FIND_VALID_CONFIG`)) { + gtagRenderer('cannot_launch_for_invalid_llm_config') + console.log(`大模型配置无效`, err) + ElMessageBox.confirm( + '大模型配置不存在或者包含无效配置
您是否希望查看并修正当前大模型配置?', + '', + { + confirmButtonText: '是', + cancelButtonText: '否', + type: 'warning', + closeOnClickModal: false, + dangerouslyUseHTMLString: true + } + ) + .then(async () => { + gtagRenderer('invalid_llm_config_tip_dialog_confirm') + try { + await electron.ipcRenderer.invoke('llm-config') + } catch (err) { + console.log(err) + } + }) + .catch(() => { + gtagRenderer('invalid_llm_config_tip_dialog_cancel') + }) + } else { + gtagRenderer('cannot_launch_for_check_llm_config_error', { err }) + ElMessage({ + type: 'error', + message: '大模型配置检查未通过,请重试' + }) + } + return false + } + try { + await electron.ipcRenderer.invoke('check-if-auto-remind-prompt-valid') + } catch (err) { + if (err?.message?.includes(`RESUME_PLACEHOLDER_NOT_EXIST`)) { + gtagRenderer('cannot_launch_for_no_resume_placehold') + console.log(`提示词模板无效`, err) + ElMessageBox.confirm( + '提示词模板缺少简历内容占位符:
__REPLACE_REAL_RESUME_HERE__

您是否希望还原默认的提示词模板?', + '', + { + confirmButtonText: '是', + cancelButtonText: '否', + type: 'warning', + closeOnClickModal: false, + dangerouslyUseHTMLString: true + } + ) + .then(async () => { + gtagRenderer('confirm_invalid_rt_tip_dialog') + await restoreDefaultTemplate() + }) + .catch(() => { + gtagRenderer('close_invalid_rt_tip_dialog') + }) + } else { + gtagRenderer('cannot_launch_for_check_prompt_error', { err }) + ElMessage({ + type: 'error', + message: '用于生成自动提醒消息的提示词检查未通过,请重试' + }) + } + return false + } + + return true +} + const handleSubmit = async () => { gtagRenderer('run_read_no_reply_reminder_clicked', { throttle_interval_minutes: formContent.value.autoReminder.throttleIntervalMinutes, @@ -270,93 +369,7 @@ const handleSubmit = async () => { formContent.value.autoReminder?.rechatContentSource === RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT ) { - if (!(await electron.ipcRenderer.invoke('check-is-resume-content-valid'))) { - gtagRenderer('cannot_launch_for_invalid_rc_dialog_show') - try { - await ElMessageBox.confirm(`简历内容无效;您需要编辑一下您的简历`, { - cancelButtonText: '取消', - confirmButtonText: '好的,去编辑我的简历', - dangerouslyUseHTMLString: true - }) - gtagRenderer('invalid_rc_dialog_click_confirm') - try { - await electron.ipcRenderer.invoke('resume-edit') - } catch (err) { - console.log(err) - } - } catch { - gtagRenderer('invalid_rc_dialog_click_cancel') - } - return - } - try { - await electron.ipcRenderer.invoke('check-if-llm-config-list-valid') - } catch (err) { - if (err?.message?.includes(`CANNOT_FIND_VALID_CONFIG`)) { - gtagRenderer('cannot_launch_for_invalid_llm_config') - console.log(`大模型配置无效`, err) - ElMessageBox.confirm( - '大模型配置不存在或者包含无效配置
您是否希望查看并修正当前大模型配置?', - '', - { - confirmButtonText: '是', - cancelButtonText: '否', - type: 'warning', - closeOnClickModal: false, - dangerouslyUseHTMLString: true - } - ) - .then(async () => { - gtagRenderer('invalid_llm_config_tip_dialog_confirm') - try { - await electron.ipcRenderer.invoke('llm-config') - } catch (err) { - console.log(err) - } - }) - .catch(() => { - gtagRenderer('invalid_llm_config_tip_dialog_cancel') - }) - } else { - gtagRenderer('cannot_launch_for_check_llm_config_error', { err }) - ElMessage({ - type: 'error', - message: '大模型配置检查未通过,请重试' - }) - } - return - } - try { - await electron.ipcRenderer.invoke('check-if-auto-remind-prompt-valid') - } catch (err) { - if (err?.message?.includes(`RESUME_PLACEHOLDER_NOT_EXIST`)) { - gtagRenderer('cannot_launch_for_no_resume_placehold') - console.log(`提示词模板无效`, err) - ElMessageBox.confirm( - '提示词模板缺少简历内容占位符:
__REPLACE_REAL_RESUME_HERE__

您是否希望还原默认的提示词模板?', - '', - { - confirmButtonText: '是', - cancelButtonText: '否', - type: 'warning', - closeOnClickModal: false, - dangerouslyUseHTMLString: true - } - ) - .then(async () => { - gtagRenderer('confirm_invalid_rt_tip_dialog') - await restoreDefaultTemplate() - }) - .catch(() => { - gtagRenderer('close_invalid_rt_tip_dialog') - }) - } else { - gtagRenderer('cannot_launch_for_check_prompt_error', { err }) - ElMessage({ - type: 'error', - message: '用于生成自动提醒消息的提示词检查未通过,请重试' - }) - } + if (!(await checkIsCanRun())) { return } if (!(await electron.ipcRenderer.invoke('resume-content-enough-detect'))) { @@ -455,6 +468,13 @@ const rechatLlmFallbackOptions = [ value: RECHAT_LLM_FALLBACK.EXIT_REMINDER_PROGRAM } ] + +async function handleTestEffectClicked() { + if (!(await checkIsCanRun())) { + return + } + electron.ipcRenderer.send('test-llm-config-effect') +} diff --git a/packages/ui/src/renderer/src/router/index.ts b/packages/ui/src/renderer/src/router/index.ts index f162633..6a45aea 100644 --- a/packages/ui/src/renderer/src/router/index.ts +++ b/packages/ui/src/renderer/src/router/index.ts @@ -31,6 +31,13 @@ const routes: Array = [ title: '简历编辑' } }, + { + path: '/readNoReplyReminderLlmMock', + component: () => import('@renderer/page/ReadNoReplyReminderLlmMock/index.vue'), + meta: { + title: '已读不回提醒器 大语言模型测试' + } + }, { path: '/main-layout', component: () => import('@renderer/page/MainLayout/index.vue'),