mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-09-07 00:17:17 +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 { hasOwn } from '@vue/shared'
|
||||||
import { createLlmConfigWindow, llmConfigWindow } from '../../../window/llmConfigWindow'
|
import { createLlmConfigWindow, llmConfigWindow } from '../../../window/llmConfigWindow'
|
||||||
import { createResumeEditorWindow, resumeEditorWindow } from '../../../window/resumeEditorWindow'
|
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 {
|
import {
|
||||||
autoReminderPromptTemplateFileName,
|
autoReminderPromptTemplateFileName,
|
||||||
writeDefaultAutoRemindPrompt
|
writeDefaultAutoRemindPrompt
|
||||||
@@ -42,6 +45,11 @@ import {
|
|||||||
checkIsResumeContentValid,
|
checkIsResumeContentValid,
|
||||||
resumeContentEnoughDetect
|
resumeContentEnoughDetect
|
||||||
} from '../../../../common/utils/resume'
|
} from '../../../../common/utils/resume'
|
||||||
|
import {
|
||||||
|
createReadNoReplyReminderLlmMockWindow,
|
||||||
|
readNoReplyReminderLlmMockWindow
|
||||||
|
} from '../../../window/readNoReplyReminderLlmMockWindow'
|
||||||
|
import { RequestSceneEnum } from '../../../features/llm-request-log'
|
||||||
|
|
||||||
export default function initIpc() {
|
export default function initIpc() {
|
||||||
ipcMain.handle('fetch-config-file-content', async () => {
|
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', () => {
|
ipcMain.handle('exit-app-immediately', () => {
|
||||||
app.exit(0)
|
app.exit(0)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export const writeDefaultAutoRemindPrompt = async () => {
|
|||||||
await writeStorageFile(autoReminderPromptTemplateFileName, defaultPrompt, { isJson: false })
|
await writeStorageFile(autoReminderPromptTemplateFileName, defaultPrompt, { isJson: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendGptContent = async (page: Page, chatRecords) => {
|
export const requestNewMessageContent = async (chatRecords, { requestScene } = {}) => {
|
||||||
const template = await getValidTemplate()
|
const template = await getValidTemplate()
|
||||||
const resumeObject = (await readConfigFile('resumes.json'))?.[0]
|
const resumeObject = (await readConfigFile('resumes.json'))?.[0]
|
||||||
const resumeContent = formatResumeJsonToMarkdown(resumeObject)
|
const resumeContent = formatResumeJsonToMarkdown(resumeObject)
|
||||||
@@ -148,6 +148,9 @@ export const sendGptContent = async (page: Page, chatRecords) => {
|
|||||||
}
|
}
|
||||||
console.log(chatList)
|
console.log(chatList)
|
||||||
let res, llmConfig
|
let res, llmConfig
|
||||||
|
const llmRequestRecord: Omit<LlmModelUsageRecord, 'id' | 'providerApiSecretMd5'> & {
|
||||||
|
providerApiSecret: string
|
||||||
|
} = {}
|
||||||
while (!res) {
|
while (!res) {
|
||||||
const llmConfigList = await readConfigFile('llm.json')
|
const llmConfigList = await readConfigFile('llm.json')
|
||||||
llmConfig = pickLlmConfigFromList(llmConfigList)
|
llmConfig = pickLlmConfigFromList(llmConfigList)
|
||||||
@@ -155,17 +158,15 @@ export const sendGptContent = async (page: Page, chatRecords) => {
|
|||||||
throw new Error(`CANNOT_FIND_A_USABLE_MODEL`)
|
throw new Error(`CANNOT_FIND_A_USABLE_MODEL`)
|
||||||
}
|
}
|
||||||
console.log(llmConfig.providerCompleteApiUrl)
|
console.log(llmConfig.providerCompleteApiUrl)
|
||||||
const llmRequestRecord: Omit<LlmModelUsageRecord, 'id' | 'providerApiSecretMd5'> & {
|
Object.assign(llmRequestRecord, {
|
||||||
providerApiSecret: string
|
|
||||||
} = {
|
|
||||||
providerCompleteApiUrl: llmConfig.providerCompleteApiUrl,
|
providerCompleteApiUrl: llmConfig.providerCompleteApiUrl,
|
||||||
model: llmConfig.model,
|
model: llmConfig.model,
|
||||||
providerApiSecret: llmConfig.providerApiSecret,
|
providerApiSecret: llmConfig.providerApiSecret,
|
||||||
requestStartTime: new Date(),
|
requestStartTime: new Date(),
|
||||||
hasError: false,
|
hasError: false,
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
requestScene: RequestSceneEnum.readNoReplyAutoReminder
|
requestScene
|
||||||
}
|
})
|
||||||
try {
|
try {
|
||||||
const completion = await completes(
|
const completion = await completes(
|
||||||
{
|
{
|
||||||
@@ -227,6 +228,19 @@ export const sendGptContent = async (page: Page, chatRecords) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`fail to parse response. ${err?.message} ${res?.message?.content}`)
|
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 chatInputSelector = `.chat-conversation .message-controls .chat-input`
|
||||||
const chatInputHandle = await page.$(chatInputSelector)
|
const chatInputHandle = await page.$(chatInputSelector)
|
||||||
await chatInputHandle.click()
|
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>
|
||||||
<div>
|
<div>
|
||||||
<el-button size="small" type="primary" @click="handleClickEditPrompt">
|
<el-button size="small" type="primary" @click="handleClickEditPrompt">
|
||||||
使用外部编辑器编辑提示词模板
|
使用外部编辑器编辑提示词模板 (Markdown)
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -133,6 +133,11 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button size="small" type="primary" @click="handleTestEffectClicked"
|
||||||
|
>使用当前配置模拟已读不回复聊过程</el-button
|
||||||
|
>
|
||||||
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<el-form-item label="跟进间隔(分钟)" prop="throttleIntervalMinutes">
|
<el-form-item label="跟进间隔(分钟)" prop="throttleIntervalMinutes">
|
||||||
@@ -256,20 +261,7 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
async function checkIsCanRun() {
|
||||||
gtagRenderer('run_read_no_reply_reminder_clicked', {
|
|
||||||
throttle_interval_minutes: formContent.value.autoReminder.throttleIntervalMinutes,
|
|
||||||
rechat_limit_day: formContent.value.autoReminder.rechatLimitDay,
|
|
||||||
rechat_content_source: formContent.value.autoReminder.rechatContentSource,
|
|
||||||
recent_message_quantity_for_llm: formContent.value.autoReminder.recentMessageQuantityForLlm
|
|
||||||
})
|
|
||||||
await formRef.value!.validate()
|
|
||||||
await electron.ipcRenderer.invoke('save-config-file-from-ui', JSON.stringify(formContent.value))
|
|
||||||
gtagRenderer('config_saved')
|
|
||||||
if (
|
|
||||||
formContent.value.autoReminder?.rechatContentSource ===
|
|
||||||
RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT
|
|
||||||
) {
|
|
||||||
if (!(await electron.ipcRenderer.invoke('check-is-resume-content-valid'))) {
|
if (!(await electron.ipcRenderer.invoke('check-is-resume-content-valid'))) {
|
||||||
gtagRenderer('cannot_launch_for_invalid_rc_dialog_show')
|
gtagRenderer('cannot_launch_for_invalid_rc_dialog_show')
|
||||||
try {
|
try {
|
||||||
@@ -287,7 +279,7 @@ const handleSubmit = async () => {
|
|||||||
} catch {
|
} catch {
|
||||||
gtagRenderer('invalid_rc_dialog_click_cancel')
|
gtagRenderer('invalid_rc_dialog_click_cancel')
|
||||||
}
|
}
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await electron.ipcRenderer.invoke('check-if-llm-config-list-valid')
|
await electron.ipcRenderer.invoke('check-if-llm-config-list-valid')
|
||||||
@@ -324,7 +316,7 @@ const handleSubmit = async () => {
|
|||||||
message: '大模型配置检查未通过,请重试'
|
message: '大模型配置检查未通过,请重试'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await electron.ipcRenderer.invoke('check-if-auto-remind-prompt-valid')
|
await electron.ipcRenderer.invoke('check-if-auto-remind-prompt-valid')
|
||||||
@@ -357,6 +349,27 @@ const handleSubmit = async () => {
|
|||||||
message: '用于生成自动提醒消息的提示词检查未通过,请重试'
|
message: '用于生成自动提醒消息的提示词检查未通过,请重试'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
gtagRenderer('run_read_no_reply_reminder_clicked', {
|
||||||
|
throttle_interval_minutes: formContent.value.autoReminder.throttleIntervalMinutes,
|
||||||
|
rechat_limit_day: formContent.value.autoReminder.rechatLimitDay,
|
||||||
|
rechat_content_source: formContent.value.autoReminder.rechatContentSource,
|
||||||
|
recent_message_quantity_for_llm: formContent.value.autoReminder.recentMessageQuantityForLlm
|
||||||
|
})
|
||||||
|
await formRef.value!.validate()
|
||||||
|
await electron.ipcRenderer.invoke('save-config-file-from-ui', JSON.stringify(formContent.value))
|
||||||
|
gtagRenderer('config_saved')
|
||||||
|
if (
|
||||||
|
formContent.value.autoReminder?.rechatContentSource ===
|
||||||
|
RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT
|
||||||
|
) {
|
||||||
|
if (!(await checkIsCanRun())) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!(await electron.ipcRenderer.invoke('resume-content-enough-detect'))) {
|
if (!(await electron.ipcRenderer.invoke('resume-content-enough-detect'))) {
|
||||||
@@ -455,6 +468,13 @@ const rechatLlmFallbackOptions = [
|
|||||||
value: RECHAT_LLM_FALLBACK.EXIT_REMINDER_PROGRAM
|
value: RECHAT_LLM_FALLBACK.EXIT_REMINDER_PROGRAM
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
async function handleTestEffectClicked() {
|
||||||
|
if (!(await checkIsCanRun())) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
electron.ipcRenderer.send('test-llm-config-effect')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<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: '简历编辑'
|
title: '简历编辑'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/readNoReplyReminderLlmMock',
|
||||||
|
component: () => import('@renderer/page/ReadNoReplyReminderLlmMock/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '已读不回提醒器 大语言模型测试'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/main-layout',
|
path: '/main-layout',
|
||||||
component: () => import('@renderer/page/MainLayout/index.vue'),
|
component: () => import('@renderer/page/MainLayout/index.vue'),
|
||||||
|
|||||||
Reference in New Issue
Block a user