mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-09-07 00:17:17 +08:00
add ui for multi llm config. dynamic adjust llm window height
This commit is contained in:
@@ -4,6 +4,7 @@ import { createMainWindow } from '../../window/mainWindow'
|
|||||||
import './app-menu'
|
import './app-menu'
|
||||||
import initIpc from './ipc'
|
import initIpc from './ipc'
|
||||||
import gtag from '../../utils/gtag'
|
import gtag from '../../utils/gtag'
|
||||||
|
import initPublicIpc from '../../utils/initPublicIpc'
|
||||||
export function openSettingWindow() {
|
export function openSettingWindow() {
|
||||||
// TODO: singleton lock; how can we check if there is another process should run as singleton with arguments?
|
// TODO: singleton lock; how can we check if there is another process should run as singleton with arguments?
|
||||||
if (!app.requestSingleInstanceLock()) {
|
if (!app.requestSingleInstanceLock()) {
|
||||||
@@ -31,6 +32,7 @@ export function openSettingWindow() {
|
|||||||
|
|
||||||
// IPC test
|
// IPC test
|
||||||
ipcMain.on('ping', () => console.log('pong'))
|
ipcMain.on('ping', () => console.log('pong'))
|
||||||
|
initPublicIpc()
|
||||||
initIpc()
|
initIpc()
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
|
|||||||
@@ -445,11 +445,7 @@ export default function initIpc() {
|
|||||||
})
|
})
|
||||||
const defer = Promise.withResolvers()
|
const defer = Promise.withResolvers()
|
||||||
async function saveLlmConfigHandler(_, configToSave) {
|
async function saveLlmConfigHandler(_, configToSave) {
|
||||||
const configToPatch = await readConfigFile('llm.json')
|
await writeConfigFile('llm.json', configToSave)
|
||||||
for (const k of Object.keys(configToSave)) {
|
|
||||||
configToPatch[k] = configToSave[k]
|
|
||||||
}
|
|
||||||
await writeConfigFile('llm.json', configToPatch)
|
|
||||||
defer.resolve()
|
defer.resolve()
|
||||||
ipcMain.removeHandler('save-llm-config')
|
ipcMain.removeHandler('save-llm-config')
|
||||||
llmConfigWindow?.close()
|
llmConfigWindow?.close()
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { BrowserWindow, ipcMain } from 'electron'
|
||||||
|
|
||||||
|
export default function initPublicIpc() {
|
||||||
|
ipcMain.on(
|
||||||
|
'update-window-size',
|
||||||
|
(
|
||||||
|
ev,
|
||||||
|
size: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
animate?: boolean
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
const win = BrowserWindow.fromWebContents(ev.sender)
|
||||||
|
if (!win) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
win.setSize(size.width, size.height, size.animate)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -47,14 +47,5 @@ export const initIpc = () => {
|
|||||||
createFirstLaunchNoticeApproveFlag()
|
createFirstLaunchNoticeApproveFlag()
|
||||||
firstLaunchNoticeWindow?.close()
|
firstLaunchNoticeWindow?.close()
|
||||||
})
|
})
|
||||||
ipcMain.on('update-window-size', (ev, size: {
|
|
||||||
width: number, height: number, animate?: boolean
|
|
||||||
}) => {
|
|
||||||
const win = BrowserWindow.fromWebContents(ev.sender)
|
|
||||||
if (!win) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
win.setSize(size.width, size.height, size.animate)
|
|
||||||
} )
|
|
||||||
}
|
}
|
||||||
initIpc()
|
initIpc()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function createLlmConfigWindow(
|
|||||||
}
|
}
|
||||||
llmConfigWindow = new BrowserWindow({
|
llmConfigWindow = new BrowserWindow({
|
||||||
width: 576,
|
width: 576,
|
||||||
height: 800,
|
height: 410,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
show: false,
|
show: false,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
|
|||||||
@@ -1,124 +1,181 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="llm-config-page">
|
<div class="llm-config-page">
|
||||||
<div class="mt1em mb1em">
|
<div class="main-wrapper">
|
||||||
<span>大语言模型设置</span>
|
<main>
|
||||||
</div>
|
<div class="mt1em mb1em">
|
||||||
<el-form
|
<span>大语言模型设置</span>
|
||||||
ref="formRef"
|
</div>
|
||||||
:model="formContent"
|
<el-form
|
||||||
:rules="formRules"
|
ref="formRef"
|
||||||
label-position="top"
|
:model="formContent"
|
||||||
class="llm-config-form"
|
:rules="formRules"
|
||||||
>
|
label-position="top"
|
||||||
<div v-for="(conf, index) in formContent" :key="index">
|
class="llm-config-form"
|
||||||
<div>
|
>
|
||||||
<el-form-item prop="providerCompleteApiUrl">
|
<div v-for="(conf, index) in formContent" :key="index" class="flex gap12px">
|
||||||
<div
|
<div
|
||||||
class="el-form-item__label flex-items-center flex-justify-between w-full pr0px"
|
v-if="formContent.length > 1"
|
||||||
:style="{ display: 'flex' }"
|
:style="{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '10px',
|
||||||
|
height: 'fit-content',
|
||||||
|
marginTop: '10px'
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div>服务提供商 Base Url</div>
|
<el-button
|
||||||
<el-dropdown @command="(item) => handlePresetClick(item, index)">
|
:disabled="index <= 0"
|
||||||
<el-button size="small"
|
style="margin: 0"
|
||||||
>配置模板 <el-icon class="el-icon--right"><arrow-down /></el-icon
|
circle
|
||||||
></el-button>
|
size="small"
|
||||||
<template #dropdown>
|
:icon="ArrowUp"
|
||||||
<el-dropdown-menu>
|
@click="moveConfigUp(index)"
|
||||||
<el-dropdown-item
|
/>
|
||||||
v-for="item in llmPresetList"
|
<el-button
|
||||||
:key="item.name"
|
:disabled="index >= formContent.length - 1"
|
||||||
:command="item"
|
style="margin: 0"
|
||||||
>{{ item.name }}</el-dropdown-item
|
circle
|
||||||
>
|
size="small"
|
||||||
</el-dropdown-menu>
|
:icon="ArrowDown"
|
||||||
</template>
|
@click="moveConfigDown(index)"
|
||||||
</el-dropdown>
|
/>
|
||||||
|
<el-button
|
||||||
|
:disabled="1 >= formContent.length"
|
||||||
|
style="margin: 0"
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
:icon="Delete"
|
||||||
|
@click="removeConfig(index)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<el-input
|
<div class="w-full">
|
||||||
v-model="conf.providerCompleteApiUrl"
|
<el-form-item prop="providerCompleteApiUrl">
|
||||||
:autosize="{
|
<div
|
||||||
minRows: 10,
|
class="el-form-item__label flex-items-center flex-justify-between w-full pr0px"
|
||||||
maxRows: 10
|
:style="{ display: 'flex' }"
|
||||||
}"
|
>
|
||||||
font-size-12px
|
<div>服务提供商 Base Url</div>
|
||||||
></el-input>
|
<el-dropdown @command="(item) => handlePresetClick(item, index)">
|
||||||
</el-form-item>
|
<el-button size="small"
|
||||||
<el-form-item prop="model" label="要使用的模型">
|
>配置模板 <el-icon class="el-icon--right"><arrow-down /></el-icon
|
||||||
<el-input
|
></el-button>
|
||||||
v-model="conf.model"
|
<template #dropdown>
|
||||||
:autosize="{
|
<el-dropdown-menu>
|
||||||
minRows: 10,
|
<el-dropdown-item
|
||||||
maxRows: 10
|
v-for="item in llmPresetList"
|
||||||
}"
|
:key="item.name"
|
||||||
font-size-12px
|
:command="item"
|
||||||
></el-input>
|
>{{ item.name }}</el-dropdown-item
|
||||||
</el-form-item>
|
>
|
||||||
<el-form-item prop="providerApiSecret" label="从服务提供商处获取的 API Secret">
|
</el-dropdown-menu>
|
||||||
<el-input
|
</template>
|
||||||
v-model="conf.providerApiSecret"
|
</el-dropdown>
|
||||||
:autosize="{
|
</div>
|
||||||
minRows: 10,
|
<el-input
|
||||||
maxRows: 10
|
ref="firstInputRefList"
|
||||||
}"
|
v-model="conf.providerCompleteApiUrl"
|
||||||
font-size-12px
|
|
||||||
></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<div class="serve-weight-config">
|
|
||||||
<div class="flex">
|
|
||||||
<el-form-item prop="enabled">
|
|
||||||
<div class="el-form-item__label">启用</div>
|
|
||||||
<el-checkbox
|
|
||||||
v-model="conf.enabled"
|
|
||||||
:autosize="{
|
:autosize="{
|
||||||
minRows: 10,
|
minRows: 10,
|
||||||
maxRows: 10
|
maxRows: 10
|
||||||
}"
|
}"
|
||||||
font-size-12px
|
font-size-12px
|
||||||
></el-checkbox>
|
></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="serveWeight" class="ml40px">
|
<el-form-item prop="model" label="要使用的模型">
|
||||||
<div class="el-form-item__label">权重</div>
|
<el-input
|
||||||
<el-input-number
|
v-model="conf.model"
|
||||||
v-model="conf.serveWeight"
|
|
||||||
:autosize="{
|
:autosize="{
|
||||||
minRows: 10,
|
minRows: 10,
|
||||||
maxRows: 10
|
maxRows: 10
|
||||||
}"
|
}"
|
||||||
:min="0"
|
|
||||||
:max="100"
|
|
||||||
:step="1"
|
|
||||||
step-strictly
|
|
||||||
:precision="0"
|
|
||||||
font-size-12px
|
font-size-12px
|
||||||
placeholder="0 ~ 100"
|
></el-input>
|
||||||
></el-input-number>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
<el-form-item prop="providerApiSecret" label="从服务提供商处获取的 API Secret">
|
||||||
<div class="flex">
|
<el-input
|
||||||
<!-- <el-form-item class="ml20px">
|
v-model="conf.providerApiSecret"
|
||||||
<el-button type="text">测试设置</el-button>
|
:autosize="{
|
||||||
</el-form-item> -->
|
minRows: 10,
|
||||||
|
maxRows: 10
|
||||||
|
}"
|
||||||
|
font-size-12px
|
||||||
|
></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<div v-if="formContent.length > 1" class="serve-weight-config">
|
||||||
|
<div class="flex">
|
||||||
|
<el-form-item prop="enabled">
|
||||||
|
<div class="el-form-item__label">启用</div>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="conf.enabled"
|
||||||
|
:autosize="{
|
||||||
|
minRows: 10,
|
||||||
|
maxRows: 10
|
||||||
|
}"
|
||||||
|
font-size-12px
|
||||||
|
></el-checkbox>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="serveWeight" class="ml40px">
|
||||||
|
<div class="el-form-item__label">权重</div>
|
||||||
|
<el-input-number
|
||||||
|
v-model="conf.serveWeight"
|
||||||
|
:autosize="{
|
||||||
|
minRows: 10,
|
||||||
|
maxRows: 10
|
||||||
|
}"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:step="1"
|
||||||
|
step-strictly
|
||||||
|
:precision="0"
|
||||||
|
font-size-12px
|
||||||
|
placeholder="0 ~ 100"
|
||||||
|
></el-input-number>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<!-- <el-form-item class="ml20px">
|
||||||
|
<el-button type="text">测试设置</el-button>
|
||||||
|
</el-form-item> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="index !== formContent.length - 1"
|
||||||
|
class="mt6px mb20px h1px"
|
||||||
|
style="background-color: #dcdcdc"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<footer pt10px pb10px flex flex-justify-center>
|
||||||
|
<div w480px flex flex-justify-between>
|
||||||
|
<div>
|
||||||
|
<el-button type="text" @click="addConfig">添加其它服务</el-button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-button @click="handleCancel">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</el-form>
|
|
||||||
<footer flex mt20px pb20px flex-justify-between>
|
|
||||||
<div>
|
|
||||||
<!-- <el-button type="text" @click="handleTestAvailability">测试可用性</el-button> -->
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<el-button @click="handleCancel">取消</el-button>
|
|
||||||
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ElForm, ElDropdown, ElDropdownMenu, ElDropdownItem, ElIcon, ElButton } from 'element-plus'
|
import {
|
||||||
import { ArrowDown } from '@element-plus/icons-vue'
|
ElForm,
|
||||||
import { ref, onMounted } from 'vue'
|
ElDropdown,
|
||||||
|
ElDropdownMenu,
|
||||||
|
ElDropdownItem,
|
||||||
|
ElIcon,
|
||||||
|
ElButton,
|
||||||
|
ElInput
|
||||||
|
} from 'element-plus'
|
||||||
|
import { ArrowUp, ArrowDown, Delete } from '@element-plus/icons-vue'
|
||||||
|
import { ref, onMounted, watch, nextTick } from 'vue'
|
||||||
|
|
||||||
interface LlmConfigItem {
|
interface LlmConfigItem {
|
||||||
providerCompleteApiUrl: string
|
providerCompleteApiUrl: string
|
||||||
@@ -128,16 +185,17 @@ interface LlmConfigItem {
|
|||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
|
|
||||||
const formRef = ref<InstanceType<typeof ElForm>>()
|
function getNewConfigItem(): LlmConfigItem {
|
||||||
const formContent = ref<LlmConfigItem[]>([
|
return {
|
||||||
{
|
|
||||||
providerCompleteApiUrl: '',
|
providerCompleteApiUrl: '',
|
||||||
providerApiSecret: '',
|
providerApiSecret: '',
|
||||||
model: '',
|
model: '',
|
||||||
serveWeight: 10,
|
serveWeight: 10,
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
])
|
}
|
||||||
|
const formRef = ref<InstanceType<typeof ElForm>>()
|
||||||
|
const formContent = ref<LlmConfigItem[]>([getNewConfigItem()])
|
||||||
|
|
||||||
const formRules = {}
|
const formRules = {}
|
||||||
|
|
||||||
@@ -151,12 +209,17 @@ const handleSubmit = async () => {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const savedFileContent = (await electron.ipcRenderer.invoke('fetch-config-file-content'))
|
const savedFileContent = (await electron.ipcRenderer.invoke('fetch-config-file-content'))
|
||||||
?.config?.['llm.json']
|
?.config?.['llm.json']
|
||||||
if (!savedFileContent) {
|
if (!savedFileContent?.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (const k of Object.keys(formContent.value)) {
|
const keyOfItem = Object.keys(getNewConfigItem())
|
||||||
formContent.value[k] = savedFileContent[k]
|
formContent.value = savedFileContent.map((it) => {
|
||||||
}
|
const conf = {}
|
||||||
|
for (const k of keyOfItem) {
|
||||||
|
conf[k] = it[k]
|
||||||
|
}
|
||||||
|
return conf
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const llmPresetList: {
|
const llmPresetList: {
|
||||||
@@ -202,13 +265,71 @@ function handlePresetClick(selected: (typeof llmPresetList)[number], index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const firstInputRefList = ref<InstanceType<typeof ElInput>[]>([])
|
||||||
|
function addConfig() {
|
||||||
|
formContent.value.push(getNewConfigItem())
|
||||||
|
nextTick(() => {
|
||||||
|
firstInputRefList.value[firstInputRefList.value.length - 1]?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function moveConfigUp(index) {
|
||||||
|
;[formContent.value[index], formContent.value[index - 1]] = [
|
||||||
|
formContent.value[index - 1],
|
||||||
|
formContent.value[index]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveConfigDown(index) {
|
||||||
|
;[formContent.value[index], formContent.value[index + 1]] = [
|
||||||
|
formContent.value[index + 1],
|
||||||
|
formContent.value[index]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeConfig(index) {
|
||||||
|
formContent.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => formContent.value.length,
|
||||||
|
(nVal) => {
|
||||||
|
if (nVal <= 1) {
|
||||||
|
electron.ipcRenderer.send('update-window-size', {
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: 360
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
electron.ipcRenderer.send('update-window-size', {
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: 730
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// function handleTestAvailability() {}
|
// function handleTestAvailability() {}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.llm-config-page {
|
.llm-config-page {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 480px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
.main-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
main {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 480px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -167,13 +167,13 @@
|
|||||||
font-size-12px
|
font-size-12px
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<div
|
||||||
|
v-if="index !== formContent.geekWorkExpList.length - 1"
|
||||||
|
class="mt20px mb20px h1px"
|
||||||
|
style="background-color: #dcdcdc"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="index !== formContent.geekWorkExpList.length - 1"
|
|
||||||
class="mt20px mb20px h1px"
|
|
||||||
style="background-color: #dcdcdc"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
@@ -283,13 +283,13 @@
|
|||||||
font-size-12px
|
font-size-12px
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<div
|
||||||
|
v-if="index !== formContent.geekProjExpList.length - 1"
|
||||||
|
class="mt20px mb20px h1px"
|
||||||
|
style="background-color: #dcdcdc"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="index !== formContent.geekProjExpList.length - 1"
|
|
||||||
class="mt20px mb20px h1px"
|
|
||||||
style="background-color: #dcdcdc"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|||||||
Reference in New Issue
Block a user