mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-06-10 10:11:31 +08:00
add basic llm testing window
This commit is contained in:
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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<LlmModelUsageRecord, 'id' | 'providerApiSecretMd5'> & {
|
||||
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<LlmModelUsageRecord, 'id' | 'providerApiSecretMd5'> & {
|
||||
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()
|
||||
|
||||
@@ -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!
|
||||
}
|
||||
@@ -81,7 +81,7 @@
|
||||
<div>
|
||||
<div>
|
||||
<el-button size="small" type="primary" @click="handleClickEditPrompt">
|
||||
使用外部编辑器编辑提示词模板
|
||||
使用外部编辑器编辑提示词模板 (Markdown)
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
@@ -133,6 +133,11 @@
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="small" type="primary" @click="handleTestEffectClicked"
|
||||
>使用当前配置模拟已读不回复聊过程</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</div>
|
||||
<el-form-item label="跟进间隔(分钟)" prop="throttleIntervalMinutes">
|
||||
@@ -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(
|
||||
'大模型配置不存在或者包含无效配置<br />您是否希望查看并修正当前大模型配置?',
|
||||
'',
|
||||
{
|
||||
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(
|
||||
'提示词模板缺少简历内容占位符:<br /><b>__REPLACE_REAL_RESUME_HERE__</b><br /><br />您是否希望还原默认的提示词模板?',
|
||||
'',
|
||||
{
|
||||
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(
|
||||
'大模型配置不存在或者包含无效配置<br />您是否希望查看并修正当前大模型配置?',
|
||||
'',
|
||||
{
|
||||
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(
|
||||
'提示词模板缺少简历内容占位符:<br /><b>__REPLACE_REAL_RESUME_HERE__</b><br /><br />您是否希望还原默认的提示词模板?',
|
||||
'',
|
||||
{
|
||||
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')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="h100vh flex flex-col">
|
||||
<div
|
||||
:style="{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
overflow: `auto`,
|
||||
margin: `0 auto`,
|
||||
alignItems: `flex-end`
|
||||
}"
|
||||
>
|
||||
<div class="pb20px"></div>
|
||||
<div v-for="(item, index) in messageList" :key="index" class="message-item">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
<div class="pb20px"></div>
|
||||
</div>
|
||||
<div
|
||||
:style="{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '100px 1fr',
|
||||
height: `fit-content`,
|
||||
paddingTop: `10px`,
|
||||
paddingBottom: `10px`,
|
||||
backgroundColor: `#f0f0f0`
|
||||
}"
|
||||
>
|
||||
<!-- <el-select v-model="selectedLlmConfig">
|
||||
<el-option v-for="(it, index) in llmConfigList" :key="index" :label="it">
|
||||
<div>{{ it.model }}</div>
|
||||
<div>{{ it.providerCompleteApiUrl }}</div>
|
||||
</el-option>
|
||||
</el-select> -->
|
||||
<el-button ml20px type="text" @click="closeWindow">关闭对话框</el-button>
|
||||
<el-button
|
||||
:loading="isLoading"
|
||||
mr10px
|
||||
width-fit-content
|
||||
type="primary"
|
||||
@click="sendLlmGeneratedContent"
|
||||
>
|
||||
<template v-if="isLoading">正在生成消息,请稍候...</template>
|
||||
<template v-else-if="!messageList.length">发送开场白</template>
|
||||
<template v-else>发送下一句提醒内容</template>
|
||||
</el-button>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
type MessageItem = {
|
||||
text: string
|
||||
generatedBy: string
|
||||
}
|
||||
const messageList = ref<MessageItem[]>([])
|
||||
|
||||
// const llmConfigList = ref([])
|
||||
// async function getLlmConfigList() {
|
||||
// debugger
|
||||
// llmConfigList.value = await electron.ipcRenderer.invoke('get-llm-config-for-test')
|
||||
// }
|
||||
// getLlmConfigList().catch(() => {})
|
||||
// const selectedLlmConfig = ref()
|
||||
|
||||
const isLoading = ref(false)
|
||||
async function sendLlmGeneratedContent() {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response = await electron.ipcRenderer.invoke('request-llm-for-test', {
|
||||
messageList: JSON.parse(JSON.stringify(messageList.value ?? []))
|
||||
})
|
||||
console.log(response)
|
||||
messageList.value.push({
|
||||
text: response.responseText,
|
||||
generatedBy: response.usedLlmConfig
|
||||
})
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function closeWindow() {
|
||||
electron.ipcRenderer.send(`close-read-no-reply-reminder-llm-mock-window`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.message-item {
|
||||
line-height: 1.25em;
|
||||
font-size: 14px;
|
||||
background-color: #d1f0ef;
|
||||
color: #333;
|
||||
padding: 10px;
|
||||
border-radius: 8px 8px 0 8px;
|
||||
margin-top: 20px;
|
||||
max-width: 420px;
|
||||
}
|
||||
</style>
|
||||
@@ -31,6 +31,13 @@ const routes: Array<RouteRecordRaw> = [
|
||||
title: '简历编辑'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/readNoReplyReminderLlmMock',
|
||||
component: () => import('@renderer/page/ReadNoReplyReminderLlmMock/index.vue'),
|
||||
meta: {
|
||||
title: '已读不回提醒器 大语言模型测试'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/main-layout',
|
||||
component: () => import('@renderer/page/MainLayout/index.vue'),
|
||||
|
||||
Reference in New Issue
Block a user