Desktop client reads llm configuration from the config/config.toml file

This commit is contained in:
aylvn 2025-03-17 23:15:14 +08:00
parent 7e42e4ccd2
commit 2d507c0bcf
10 changed files with 432 additions and 168 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"OpenManus/src/utils"
"context" "context"
"fmt" "fmt"
) )
@ -10,6 +11,12 @@ type App struct {
ctx context.Context ctx context.Context
} }
type File struct {
Result string `json:"result"`
Error string `json:"error"`
Callbackid string `json:"callbackid"`
}
// NewApp creates a new App application struct // NewApp creates a new App application struct
func NewApp() *App { func NewApp() *App {
return &App{} return &App{}
@ -25,3 +32,11 @@ func (a *App) startup(ctx context.Context) {
func (a *App) Greet(name string) string { func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s, It's show time!", name) return fmt.Sprintf("Hello %s, It's show time!", name)
} }
// ReadAll reads file content
func (a *App) ReadAll(filePath string) string {
// 读取文件内容,得到一个含文件内容和callbackid的json字符串
data := string(utils.ReadAll(filePath))
utils.Log("ReadAll data: ", data)
return data
}

View File

@ -1,201 +1,206 @@
import utils from '@/assets/js/utils' import utils from '@/assets/js/utils'
import { ReadAll } from '@/../wailsjs/go/main/App.js'
// 临时缓存文件信息 // 临时缓存文件信息
function cache(fileObj, $event) { function cache(fileObj, $event) {
console.log('cache fileObj start:', fileObj, $event.target, $event.dataTransfer) console.log('cache fileObj start:', fileObj, $event.target, $event.dataTransfer)
console.log('typeof fileObj:', Array.isArray(fileObj)) console.log('typeof fileObj:', Array.isArray(fileObj))
// 如果fileObj是数组,创建一个新的元素,追加到数组 // 如果fileObj是数组,创建一个新的元素,追加到数组
// event.target.files和event.dataTransfer.files是JavaScript中与文件上传和拖放相关的事件属性。 // event.target.files和event.dataTransfer.files是JavaScript中与文件上传和拖放相关的事件属性。
// event.target.files这个属性是在HTML的文件输入元素<input type="file">)上使用时, // event.target.files这个属性是在HTML的文件输入元素<input type="file">)上使用时,
// 当用户选择文件并触发change事件时可以通过event.target.files获取到用户选择的文件列表。 // 当用户选择文件并触发change事件时可以通过event.target.files获取到用户选择的文件列表。
// event.dataTransfer.files这个属性是在用户拖放文件到一个元素上时 // event.dataTransfer.files这个属性是在用户拖放文件到一个元素上时
// 可以通过event.dataTransfer.files获取到拖放的文件列表。 // 可以通过event.dataTransfer.files获取到拖放的文件列表。
console.log('$event:', $event, $event.type) console.log('$event:', $event, $event.type)
let files let files
if ($event.type == 'change') { if ($event.type == 'change') {
files = $event.target.files files = $event.target.files
} else if ($event.type == 'drop') { } else if ($event.type == 'drop') {
files = $event.dataTransfer.files files = $event.dataTransfer.files
} else { } else {
console.error("无法识别的事件") console.error("无法识别的事件")
return return
} }
const file = files[0] const file = files[0]
console.log("file:", file) console.log("file:", file)
const fileInfo = Array.isArray(fileObj) ? new Object() : fileObj const fileInfo = Array.isArray(fileObj) ? new Object() : fileObj
fileInfo.file = file fileInfo.file = file
let URL = window.URL || window.webkitURL let URL = window.URL || window.webkitURL
fileInfo.fileUrl = URL.createObjectURL(file) fileInfo.fileUrl = URL.createObjectURL(file)
const fileType = file.type const fileType = file.type
console.log(fileType, typeof (fileType)) console.log(fileType, typeof (fileType))
if (utils.notNull(fileType) && fileType.startsWith("image")) { if (utils.notNull(fileType) && fileType.startsWith("image")) {
fileInfo.imgUrl = fileInfo.fileUrl fileInfo.imgUrl = fileInfo.fileUrl
} }
fileInfo.fileName = file.name fileInfo.fileName = file.name
console.log('cache fileObj end:', fileInfo) console.log('cache fileObj end:', fileInfo)
if (Array.isArray(fileObj)) { if (Array.isArray(fileObj)) {
// 操作成功后追加到数组末尾 // 操作成功后追加到数组末尾
fileObj.push(fileInfo) fileObj.push(fileInfo)
} }
if ($event.type == 'change') { if ($event.type == 'change') {
// 解决选择相同的文件 不触发change事件的问题,放在最后清理 // 解决选择相同的文件 不触发change事件的问题,放在最后清理
$event.target.value = null $event.target.value = null
} }
} }
// 上传文件 // 上传文件
async function upload(fileObj) { async function upload(fileObj) {
console.log("准备开始上传文件!", fileObj, fileObj.file, fileObj.fileId) console.log("准备开始上传文件!", fileObj, fileObj.file, fileObj.fileId)
// 当前地址 // 当前地址
if (utils.isNull(fileObj.file)) { if (utils.isNull(fileObj.file)) {
if (utils.notNull(fileObj.fileId) && fileObj.remark != fileObj.remarkUpd) { if (utils.notNull(fileObj.fileId) && fileObj.remark != fileObj.remarkUpd) {
let remark = null let remark = null
if (utils.notNull(fileObj.remarkUpd)) { if (utils.notNull(fileObj.remarkUpd)) {
remark = fileObj.remarkUpd remark = fileObj.remarkUpd
} }
await updRemark(fileObj.fileId, remark) await updRemark(fileObj.fileId, remark)
}
return
} }
console.log("开始上传文件!", fileObj, fileObj.file, fileObj.fileId) return
const url = '/common/file/upload' }
const formData = new FormData() console.log("开始上传文件!", fileObj, fileObj.file, fileObj.fileId)
formData.append('file', fileObj.file) const url = '/common/file/upload'
if (utils.notNull(fileObj.remark)) { const formData = new FormData()
formData.append('remark', fileObj.remark) formData.append('file', fileObj.file)
} else if (utils.notNull(fileObj.remarkUpd)) { if (utils.notNull(fileObj.remark)) {
formData.append('remark', fileObj.remarkUpd) formData.append('remark', fileObj.remark)
} else if (utils.notNull(fileObj.remarkUpd)) {
formData.append('remark', fileObj.remarkUpd)
}
const data = await utils.awaitPost(url, formData, {
headers: {
'Content-Type': 'multipart/form-data'
} }
const data = await utils.awaitPost(url, formData, { })
headers: { Object.assign(fileObj, data)
'Content-Type': 'multipart/form-data' console.log("文件同步上传处理完毕", fileObj)
} return fileObj
})
Object.assign(fileObj, data)
console.log("文件同步上传处理完毕", fileObj)
return fileObj
} }
// 更新文件备注 // 更新文件备注
async function updRemark(fileId, remarkUpd) { async function updRemark(fileId, remarkUpd) {
const param = { const param = {
fileId: fileId, fileId: fileId,
remark: remarkUpd remark: remarkUpd
} }
await utils.awaitPost('/common/file/updRemark', param) await utils.awaitPost('/common/file/updRemark', param)
console.log("更新文件备注成功") console.log("更新文件备注成功")
} }
// 批量上传文件 // 批量上传文件
async function uploads(fileObjs) { async function uploads(fileObjs) {
if (utils.isEmpty(fileObjs)) { if (utils.isEmpty(fileObjs)) {
return return
} }
for (let index in fileObjs) { for (let index in fileObjs) {
console.log('fileObjs[index]:', fileObjs, index, fileObjs.length, fileObjs[index]) console.log('fileObjs[index]:', fileObjs, index, fileObjs.length, fileObjs[index])
await upload(fileObjs[index]) await upload(fileObjs[index])
console.log("uploads index:", index, "上传文件完毕", fileObjs[index]) console.log("uploads index:", index, "上传文件完毕", fileObjs[index])
} }
} }
// 上传文件(onChange时) // 上传文件(onChange时)
function upOnChg(fileObj, $event) { function upOnChg(fileObj, $event) {
const file = $event.target.files[0] || $event.dataTransfer.files[0] const file = $event.target.files[0] || $event.dataTransfer.files[0]
// 当前地址 // 当前地址
let URL = window.URL || window.webkitURL let URL = window.URL || window.webkitURL
// 转成 blob地址 // 转成 blob地址
fileObj.fileUrl = URL.createObjectURL(file) fileObj.fileUrl = URL.createObjectURL(file)
const url = '/common/file/upload' const url = '/common/file/upload'
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', file)
formData.append('remark', fileObj.remark) formData.append('remark', fileObj.remark)
utils.post(url, formData, { utils.post(url, formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
} }
}).then((data) => { }).then((data) => {
console.log("文件上传结果:", data) console.log("文件上传结果:", data)
Object.assign(fileObj, data) Object.assign(fileObj, data)
fileObj.remarkUpd = data.remark fileObj.remarkUpd = data.remark
}) })
} }
function add(fileList) { function add(fileList) {
const comp = { const comp = {
index: fileList.length, index: fileList.length,
file: null, file: null,
fileId: null, fileId: null,
fileName: null, fileName: null,
fileUrl: null, fileUrl: null,
imgUrl: null, imgUrl: null,
remark: null remark: null
} }
fileList.push(comp) fileList.push(comp)
} }
function del(fileObj, index) { function del(fileObj, index) {
console.log("fileObj,index:", fileObj, index) console.log("fileObj,index:", fileObj, index)
if (Array.isArray(fileObj)) { if (Array.isArray(fileObj)) {
fileObj.splice(index, 1) fileObj.splice(index, 1)
} else { } else {
utils.clearProps(fileObj) utils.clearProps(fileObj)
} }
} }
function trans(javaFile, jsFile) { function trans(javaFile, jsFile) {
if (jsFile == undefined || jsFile == null) { if (jsFile == undefined || jsFile == null) {
return return
} }
// 如果是数组,先清空数组 // 如果是数组,先清空数组
if (jsFile instanceof Array) { if (jsFile instanceof Array) {
jsFile.splice(0, jsFile.length) jsFile.splice(0, jsFile.length)
} else { } else {
utils.clearProps(jsFile) utils.clearProps(jsFile)
} }
if (javaFile == undefined || javaFile == null) { if (javaFile == undefined || javaFile == null) {
return return
} }
// 数组类型 // 数组类型
if (jsFile instanceof Array) { if (jsFile instanceof Array) {
for (let java of javaFile) { for (let java of javaFile) {
const js = {} const js = {}
java.remarkUpd = java.remark java.remarkUpd = java.remark
Object.assign(js, java) Object.assign(js, java)
jsFile.push(js) jsFile.push(js)
}
} else {
// 对象类型
console.log("对象类型", jsFile instanceof Array)
javaFile.remarkUpd = javaFile.remark
Object.assign(jsFile, javaFile)
} }
} else {
// 对象类型
console.log("对象类型", jsFile instanceof Array)
javaFile.remarkUpd = javaFile.remark
Object.assign(jsFile, javaFile)
}
} }
// 从Comps中收集fileId // 从Comps中收集fileId
function fileIds(fileList) { function fileIds(fileList) {
return fileList.map(comp => comp.fileId).join(',') return fileList.map(comp => comp.fileId).join(',')
}
function readAll(filePath) {
return ReadAll(filePath)
} }
export default { export default {
// onChange时缓存
// onChange时缓存 cache,
cache, // 上传文件
// 上传文件 upload,
upload, // 上传文件
// 上传文件 uploads,
uploads, // 上传文件
// 上传文件 upOnChg,
upOnChg, // onChange时上传
// onChange时上传 upOnChg,
upOnChg, // 添加到组件列表
// 添加到组件列表 add,
add, // 从组件列表中删除组件
// 从组件列表中删除组件 del,
del, // 文件Java对象与js对象转换
// 文件Java对象与js对象转换 trans,
trans, // 从Comps中收集fileId
// 从Comps中收集fileId fileIds,
fileIds // 读取文件
readAll
} }

View File

@ -494,6 +494,13 @@ function debounce(func, delay) {
} }
} }
function stringToLines(str) {
if (str == undefined || str == null) {
return []
}
return str.split('\n')
}
export default { export default {
/** /**
* http请求 GET请求 * http请求 GET请求
@ -636,4 +643,6 @@ export default {
debounce, debounce,
stringToLines,
} }

View File

@ -3,30 +3,213 @@
<el-card> <el-card>
<template #header> <template #header>
<div class="title fxsb"> <div class="title fxsb">
<div>基本信息</div> <div>LLM Config</div>
<div> <div>
<el-link type="primary" class="no-select plr-6" @click="clearCache()">清理缓存</el-link> <el-link type="primary" class="no-select plr-6" @click="toEdit('base')" v-show="baseShow">
{{ t('edit') }}
</el-link>
<el-link type="primary" class="no-select plr-6" @click="toShow('base')" v-show="baseEdit">
{{ t('cancel') }}
</el-link>
</div> </div>
</div> </div>
</template> </template>
<!-- 展示模块-无数据 -->
<div class="no-data" v-show="baseNoData">{{ t('noData') }}</div>
<!-- 展示模块-有数据 -->
<div class="card-row-wrap" v-show="baseShow">
<div class="card-row-item">
<el-text>model:</el-text>
<el-text>{{ llmConfig.model }}</el-text>
</div>
<div class="card-row-item">
<el-text>base_url:</el-text>
<el-text tag="p">{{ llmConfig.base_url }}</el-text>
</div>
<div class="card-row-item">
<el-text>api_key:</el-text>
<el-text>{{ llmConfig.api_key }}</el-text>
</div>
<div class="card-row-item">
<el-text>max_tokens:</el-text>
<el-text>{{ llmConfig.max_tokens }}</el-text>
</div>
<div class="card-row-item">
<el-text>temperature:</el-text>
<el-text>{{ llmConfig.temperature }}</el-text>
</div>
</div>
<!-- 编辑模块 -->
<el-form ref="ruleFormRef" :model="llmConfigUpd" status-icon :rules="rules" v-show="baseEdit">
<div class="card-row-wrap">
<div class="card-row-item">
<el-text>model:</el-text>
<el-form-item prop="model">
<el-input v-model="llmConfigUpd.model" />
</el-form-item>
</div>
<div class="card-row-item">
<el-text>base_url:</el-text>
<el-form-item prop="base_url">
<el-input v-model="llmConfigUpd.base_url" />
</el-form-item>
</div>
<div class="card-row-item">
<el-text>api_key:</el-text>
<el-form-item prop="api_key">
<el-input v-model="llmConfigUpd.api_key" />
</el-form-item>
</div>
<div class="card-row-item">
<el-text>max_tokens:</el-text>
<el-form-item prop="max_tokens">
<el-input v-model="llmConfigUpd.max_tokens" />
</el-form-item>
</div>
<div class="card-row-item">
<el-text>temperature:</el-text>
<el-form-item prop="temperature">
<el-input v-model="llmConfigUpd.temperature" />
</el-form-item>
</div>
<div class="card-row-aline fxc" v-show="baseEdit">
<el-button class="mlr-10" @click="toShow('base')">{{ t('cancel') }}</el-button>
<el-button type="primary" class="mlr-10" @click="submitForm">{{ t('submit') }}</el-button>
</div>
</div>
</el-form>
</el-card> </el-card>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, inject, onMounted } from 'vue' import { ref, reactive, inject, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import {useConfig} from '@/store/config' import { useConfig } from '@/store/config'
import { useI18n } from 'vue-i18n'
const utils = inject('utils') const utils = inject('utils')
const files = inject('files')
const verify = inject('verify')
const router = useRouter() const router = useRouter()
const config = useConfig() const config = useConfig()
const { t } = useI18n()
//
const viewModel = reactive({
base: 'show',
})
function toShow(model) {
console.log("toShow:" + model)
viewModel[model] = 'show'
}
function toEdit(model) {
console.log("toEdit:" + model)
viewModel[model] = 'edit'
}
const baseShow = computed(() => {
return viewModel.base == 'show' || viewModel.base == 'showMore'
})
const baseEdit = computed(() => {
return viewModel.base == 'edit'
})
const baseNoData = computed(() => {
return baseShow && llmConfig.model == null
})
const llmConfig = reactive({
model: null,
base_url: null,
api_key: null,
max_tokens: null,
temperature: null,
})
const llmConfigUpd = reactive({
model: null,
base_url: null,
api_key: null,
max_tokens: null,
temperature: null,
})
function clearCache() { function clearCache() {
config.$reset() config.$reset()
} }
onMounted(() => {
// config/config.toml
files.readAll("@/../../config/config.toml").then((fileContent) => {
console.log("config/config.toml: ", fileContent)
const lines = utils.stringToLines(fileContent)
// [llm]
const llmStart = lines.findIndex((line) => {
return line.includes("[llm]")
})
for (let i = llmStart + 1; i < lines.length; i++) {
console.log("line: ", lines[i])
//
if (lines[i].startsWith("[")) {
break
}
//
const line = lines[i]
const lineArr = line.split("=")
if (lineArr.length != 2) {
continue
}
const key = lineArr[0].trim()
const value = lineArr[1].trim()
llmConfig[key] = value
}
console.log("llmConfig read from file: ", llmConfig)
utils.copyProps(llmConfig, llmConfigUpd)
})
})
const submitForm = async () => {
try {
await ruleFormRef.value.validate();
if (!utils.hasDfProps(designSchemeDtl, designSchemeUpd)) {
ElMessage.success('未发生更改!');
toShow('base')
return
}
ElMessage.success('验证通过,提交表单');
// update()
} catch (error) {
ElMessage.error('参数验证失败');
}
}
const rules = reactive({
model: [{ validator: verify.validator('notBlank'), trigger: 'blur' }],
base_url: [{ validator: verify.validator('notBlank'), trigger: 'blur' }],
api_key: [{ validator: verify.validator('notBlank'), trigger: 'blur' }],
max_tokens: [{ validator: verify.validator('notBlank'), trigger: 'blur' }],
temperature: [{ validator: verify.validator('notBlank'), trigger: 'blur' }],
})
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -2,3 +2,5 @@
// This file is automatically generated. DO NOT EDIT // This file is automatically generated. DO NOT EDIT
export function Greet(arg1:string):Promise<string>; export function Greet(arg1:string):Promise<string>;
export function ReadAll(arg1:string):Promise<string>;

View File

@ -5,3 +5,7 @@
export function Greet(arg1) { export function Greet(arg1) {
return window['go']['main']['App']['Greet'](arg1); return window['go']['main']['App']['Greet'](arg1);
} }
export function ReadAll(arg1) {
return window['go']['main']['App']['ReadAll'](arg1);
}

29
desktop/src/utils/file.go Normal file
View File

@ -0,0 +1,29 @@
package utils
import (
"fmt"
"io"
"os"
)
// 打开文件
func ReadAll(filePath string) []byte {
if IsBlank(filePath) {
fmt.Println("File path is nil")
return nil
}
file, err := os.Open(filePath)
if err != nil {
fmt.Println("Error opening file:", err)
return nil
}
// 确保文件最后被关闭
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
fmt.Println("Read file error:", err)
return nil
}
return data
}

View File

@ -1,4 +1,4 @@
package main package utils
import ( import (
"encoding/json" "encoding/json"

View File

@ -1,4 +1,4 @@
package main package utils
import ( import (
"log" "log"

View File

@ -1,10 +1,11 @@
package main package utils
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings"
) )
// AnyToStr 任意类型数据转string // AnyToStr 任意类型数据转string
@ -45,3 +46,19 @@ func AnyToStr(i interface{}) (string, error) {
return "", fmt.Errorf("unable to cast %#v of type %T to string", i, i) return "", fmt.Errorf("unable to cast %#v of type %T to string", i, i)
} }
} }
func IsEmpty(s string) bool {
return len(s) == 0
}
func IsNotEmpty(s string) bool {
return len(s) > 0
}
func IsBlank(s string) bool {
return len(s) == 0 || strings.TrimSpace(s) == ""
}
func IsNotBlank(s string) bool {
return len(s) > 0 && strings.TrimSpace(s) != ""
}