mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-09-06 07:57:06 +08:00
add form validator for llm config
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
export const SINGLE_ITEM_DEFAULT_SERVE_WEIGHT = 1
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
writeStorageFile
|
writeStorageFile
|
||||||
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
||||||
import { formatResumeJsonToMarkdown } from '../../../common/utils/format-resume-json-to-markdown'
|
import { formatResumeJsonToMarkdown } from '../../../common/utils/format-resume-json-to-markdown'
|
||||||
|
import { SINGLE_ITEM_DEFAULT_SERVE_WEIGHT } from '../../../common/constant'
|
||||||
|
|
||||||
export const sendLookForwardReplyEmotion = async (page: Page) => {
|
export const sendLookForwardReplyEmotion = async (page: Page) => {
|
||||||
const emotionEntryButtonProxy = await page.$('.chat-conversation .message-controls .btn-emotion')
|
const emotionEntryButtonProxy = await page.$('.chat-conversation .message-controls .btn-emotion')
|
||||||
@@ -25,6 +26,8 @@ export const sendLookForwardReplyEmotion = async (page: Page) => {
|
|||||||
|
|
||||||
const pickLlmConfigFromList = (llmConfigList) => {
|
const pickLlmConfigFromList = (llmConfigList) => {
|
||||||
if (llmConfigList.length === 1) {
|
if (llmConfigList.length === 1) {
|
||||||
|
llmConfigList[0].enabled = true
|
||||||
|
llmConfigList[0].serveWeight = SINGLE_ITEM_DEFAULT_SERVE_WEIGHT
|
||||||
return llmConfigList[0]
|
return llmConfigList[0]
|
||||||
}
|
}
|
||||||
llmConfigList = llmConfigList.filter((it) => it.enabled)
|
llmConfigList = llmConfigList.filter((it) => it.enabled)
|
||||||
@@ -34,6 +37,9 @@ const pickLlmConfigFromList = (llmConfigList) => {
|
|||||||
pool.push(i)
|
pool.push(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!pool.length) {
|
||||||
|
throw new Error(`cannot find a usable model`)
|
||||||
|
}
|
||||||
const index = Math.floor(pool.length * Math.random())
|
const index = Math.floor(pool.length * Math.random())
|
||||||
return llmConfigList[
|
return llmConfigList[
|
||||||
pool[index]
|
pool[index]
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<el-button size="small"
|
<el-button size="small"
|
||||||
>申请 API Secret <el-icon class="el-icon--right"><arrow-down /></el-icon
|
>获取 API Secret <el-icon class="el-icon--right"><arrow-down /></el-icon
|
||||||
></el-button>
|
></el-button>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
@@ -63,17 +63,18 @@
|
|||||||
<li><b class="color-red">暂不支持推理模型</b>(例如 DeepSeek-R1)</li>
|
<li><b class="color-red">暂不支持推理模型</b>(例如 DeepSeek-R1)</li>
|
||||||
<li>
|
<li>
|
||||||
请自行确保您所接入的服务商能够保护您的隐私。<b class="color-red"
|
请自行确保您所接入的服务商能够保护您的隐私。<b class="color-red"
|
||||||
>此处所列举“服务商-模型”由第三方提供,仅供配置参考,不能保证它们能够合法使用您的数据,不表示本程序认可相关模型</b
|
>此处所列举“服务商-模型”由第三方提供,仅供配置参考,本程序不能保证它们能够合法使用您的数据,不表示本程序认可相关模型</b
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</el-alert>
|
</el-alert>
|
||||||
<el-form
|
<el-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="formContent"
|
:model="formContentForElForm"
|
||||||
:rules="formRules"
|
:rules="formRulesForElForm"
|
||||||
label-position="top"
|
label-position="top"
|
||||||
class="llm-config-form"
|
class="llm-config-form"
|
||||||
|
:validate-on-rule-change="false"
|
||||||
>
|
>
|
||||||
<div v-for="(conf, index) in formContent" :key="index" class="flex gap12px">
|
<div v-for="(conf, index) in formContent" :key="index" class="flex gap12px">
|
||||||
<div
|
<div
|
||||||
@@ -114,12 +115,12 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<el-form-item prop="providerCompleteApiUrl">
|
<el-form-item :prop="`${index}_providerCompleteApiUrl`">
|
||||||
<div
|
<div
|
||||||
class="el-form-item__label flex-items-center flex-justify-between w-full pr0px"
|
class="el-form-item__label flex-items-center flex-justify-between w-full pr0px"
|
||||||
:style="{ display: 'flex' }"
|
:style="{ display: 'flex' }"
|
||||||
>
|
>
|
||||||
<div>服务提供商 Base Url</div>
|
<div>服务提供商 Base URL</div>
|
||||||
<el-dropdown @command="(item) => handlePresetClick(item, index)">
|
<el-dropdown @command="(item) => handlePresetClick(item, index)">
|
||||||
<el-button size="small"
|
<el-button size="small"
|
||||||
>配置模板 <el-icon class="el-icon--right"><arrow-down /></el-icon
|
>配置模板 <el-icon class="el-icon--right"><arrow-down /></el-icon
|
||||||
@@ -166,16 +167,13 @@
|
|||||||
font-size-12px
|
font-size-12px
|
||||||
></el-input>
|
></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div v-if="formContent.length > 1" class="serve-weight-config">
|
<div class="serve-weight-config">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<el-form-item prop="enabled">
|
<el-form-item prop="enabled">
|
||||||
<div class="el-form-item__label">启用</div>
|
<div class="el-form-item__label">启用</div>
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
|
v-if="formContent.length > 1"
|
||||||
v-model="conf.enabled"
|
v-model="conf.enabled"
|
||||||
:autosize="{
|
|
||||||
minRows: 10,
|
|
||||||
maxRows: 10
|
|
||||||
}"
|
|
||||||
font-size-12px
|
font-size-12px
|
||||||
@click="
|
@click="
|
||||||
nextTick(() =>
|
nextTick(() =>
|
||||||
@@ -183,15 +181,13 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
></el-checkbox>
|
></el-checkbox>
|
||||||
|
<el-checkbox v-else :model-value="true" font-size-12px disabled />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="serveWeight" class="ml40px">
|
<el-form-item prop="serveWeight" class="ml40px">
|
||||||
<div class="el-form-item__label">权重</div>
|
<div class="el-form-item__label">权重</div>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
|
v-if="formContent.length > 1"
|
||||||
v-model="conf.serveWeight"
|
v-model="conf.serveWeight"
|
||||||
:autosize="{
|
|
||||||
minRows: 10,
|
|
||||||
maxRows: 10
|
|
||||||
}"
|
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="100"
|
:max="100"
|
||||||
:step="1"
|
:step="1"
|
||||||
@@ -205,6 +201,12 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
></el-input-number>
|
></el-input-number>
|
||||||
|
<el-input-number
|
||||||
|
v-else
|
||||||
|
font-size-12px
|
||||||
|
:model-value="SINGLE_ITEM_DEFAULT_SERVE_WEIGHT"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
@@ -247,11 +249,13 @@ import {
|
|||||||
ElDropdownItem,
|
ElDropdownItem,
|
||||||
ElIcon,
|
ElIcon,
|
||||||
ElButton,
|
ElButton,
|
||||||
ElInput
|
ElInput,
|
||||||
|
ElMessage
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
import { ArrowUp, ArrowDown, Delete } from '@element-plus/icons-vue'
|
import { ArrowUp, ArrowDown, Delete } from '@element-plus/icons-vue'
|
||||||
import { ref, onMounted, watch, nextTick } from 'vue'
|
import { ref, onMounted, watch, nextTick, computed } from 'vue'
|
||||||
import { gtagRenderer } from '@renderer/utils/gtag'
|
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||||
|
import { SINGLE_ITEM_DEFAULT_SERVE_WEIGHT } from '../../../../common/constant'
|
||||||
|
|
||||||
interface LlmConfigItem {
|
interface LlmConfigItem {
|
||||||
providerCompleteApiUrl: string
|
providerCompleteApiUrl: string
|
||||||
@@ -273,15 +277,69 @@ function getNewConfigItem(): LlmConfigItem {
|
|||||||
const formRef = ref<InstanceType<typeof ElForm>>()
|
const formRef = ref<InstanceType<typeof ElForm>>()
|
||||||
const formContent = ref<LlmConfigItem[]>([getNewConfigItem()])
|
const formContent = ref<LlmConfigItem[]>([getNewConfigItem()])
|
||||||
|
|
||||||
const formRules = {}
|
const formContentForElForm = computed(() => {
|
||||||
|
const valueMap = {}
|
||||||
|
formContent.value.forEach((configItem, i) => {
|
||||||
|
valueMap[`${i}_providerCompleteApiUrl`] = configItem.providerCompleteApiUrl
|
||||||
|
})
|
||||||
|
return valueMap
|
||||||
|
})
|
||||||
|
const formRulesForElForm = computed(() => {
|
||||||
|
const valueMap = {}
|
||||||
|
formContent.value.forEach((_, i) => {
|
||||||
|
valueMap[`${i}_providerCompleteApiUrl`] = [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入服务提供商 Base URL'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
trigger: 'blur',
|
||||||
|
validator(_, value, cb) {
|
||||||
|
try {
|
||||||
|
new URL(value?.trim())
|
||||||
|
} catch (err) {
|
||||||
|
cb(`URL 格式无效,请重新输入`)
|
||||||
|
}
|
||||||
|
if (/^http(s)?:\/\//.test(value)) {
|
||||||
|
cb()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cb(`服务提供商 Base URL 无效,请重新输入`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
return valueMap
|
||||||
|
})
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
gtagRenderer('cancel_clicked')
|
gtagRenderer('cancel_clicked')
|
||||||
electron.ipcRenderer.send('close-llm-config')
|
electron.ipcRenderer.send('close-llm-config')
|
||||||
gtagRenderer('cancel_done')
|
gtagRenderer('cancel_done')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
gtagRenderer('submit_clicked', { llm_config_length: formContent.value.length })
|
gtagRenderer('submit_clicked', { llm_config_length: formContent.value.length })
|
||||||
|
await formRef.value?.validate()
|
||||||
|
if (!formContent.value.length) {
|
||||||
|
gtagRenderer('empty_model_list')
|
||||||
|
ElMessage.warning({
|
||||||
|
message: '可选模型列表为空,请出现填写'
|
||||||
|
})
|
||||||
|
formContent.value = [getNewConfigItem()]
|
||||||
|
return
|
||||||
|
} else if (formContent.value.length > 1) {
|
||||||
|
const firstEnabledModel = formContent.value.find(it => it.enabled)
|
||||||
|
if (!firstEnabledModel) {
|
||||||
|
gtagRenderer('no_enabled_model_find_in_model_list')
|
||||||
|
ElMessage.warning({
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
grouping: true,
|
||||||
|
message: '<div style="white-space: nowrap">所有模型均被禁用;请至少启用一个模型</div>'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
electron.ipcRenderer.invoke('save-llm-config', JSON.parse(JSON.stringify(formContent.value)))
|
electron.ipcRenderer.invoke('save-llm-config', JSON.parse(JSON.stringify(formContent.value)))
|
||||||
gtagRenderer('submit_done')
|
gtagRenderer('submit_done')
|
||||||
}
|
}
|
||||||
@@ -403,11 +461,11 @@ function handlePresetClick(selected: (typeof llmPresetList)[number], index) {
|
|||||||
|
|
||||||
const firstInputRefList = ref<InstanceType<typeof ElInput>[]>([])
|
const firstInputRefList = ref<InstanceType<typeof ElInput>[]>([])
|
||||||
function addConfig() {
|
function addConfig() {
|
||||||
|
gtagRenderer('new_config_item_added', { config_list_length_before_add: formContent.value.length })
|
||||||
formContent.value.push(getNewConfigItem())
|
formContent.value.push(getNewConfigItem())
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
firstInputRefList.value[firstInputRefList.value.length - 1]?.focus()
|
firstInputRefList.value[firstInputRefList.value.length - 1]?.focus()
|
||||||
})
|
})
|
||||||
gtagRenderer('new_config_item_added')
|
|
||||||
}
|
}
|
||||||
function moveConfigUp(index) {
|
function moveConfigUp(index) {
|
||||||
;[formContent.value[index], formContent.value[index - 1]] = [
|
;[formContent.value[index], formContent.value[index - 1]] = [
|
||||||
@@ -436,7 +494,7 @@ watch(
|
|||||||
if (nVal <= 1) {
|
if (nVal <= 1) {
|
||||||
electron.ipcRenderer.send('update-window-size', {
|
electron.ipcRenderer.send('update-window-size', {
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: 510
|
height: 550
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
electron.ipcRenderer.send('update-window-size', {
|
electron.ipcRenderer.send('update-window-size', {
|
||||||
|
|||||||
Reference in New Issue
Block a user