mirror of
https://github.com/yuruotong1/autoMate.git
synced 2025-12-26 05:16:21 +08:00
commit
94a34bad55
148
README.md
148
README.md
@ -1,58 +1,87 @@
|
||||
<div align="center">
|
||||
<div align="center"><a name="readme-top"></a>
|
||||
|
||||
<a ><img src="./resources/logo.png" width="120" height="120" alt="autoMate logo"></a>
|
||||
<img src="./resources/logo.png" width="120" height="120" alt="autoMate logo">
|
||||
<h1>autoMate</h1>
|
||||
|
||||
<div style="font-size:24px;">autoMate</div>
|
||||
一个 Agent 和 RPA 开发平台
|
||||
|
||||
</div>
|
||||
<b>autoMate 就像生活出行中的共享单车,帮你完成软件的最后一个操作,只需聊聊天就能将 AI 植入任意一个软件。</b>
|
||||
|
||||
<div align="center">
|
||||
[![][issues-helper-image]][issues-helper-url] [![Issues need help][help-wanted-image]][help-wanted-url]
|
||||
|
||||
|
||||
📚[文档地址](https://s0soyusc93k.feishu.cn/wiki/JhhIwAUXJiBHG9kmt3YcXisWnec?from=from_copylink)|🎞️[介绍视频](https://www.bilibili.com/video/BV1LW421R7Ai/?share_source=copy_web&vd_source=c28e503b050f016c21660b69e391d391)
|
||||
|
||||

|
||||
|
||||
[issues-helper-image]: https://img.shields.io/badge/using-actions--cool-blue?style=flat-square
|
||||
[issues-helper-url]: https://github.com/actions-cool
|
||||
[help-wanted-image]: https://flat.badgen.net/github/label-issues/yuruotong1/autoMate/enhancement/open
|
||||
[help-wanted-url]: https://github.com/yuruotong1/autoMate/labels/enhancement
|
||||
|
||||
</div>
|
||||
|
||||
autoMate 可以生成自动化代码帮助用户减少复性劳动,他做了三件事:
|
||||
|
||||
1. 了解、分析用户需求,将需求转换为人人都能看懂的用例;
|
||||
2. 将用例转换成可执行的代码;
|
||||
3. 运行代码并调试,解决异常问题。
|
||||
|
||||
各产品优势对比如下。
|
||||
|
||||

|
||||
[](https://ant.design)
|
||||
|
||||
|
||||
## 为什么做这个项目呢?
|
||||
好多人找我,他们说影刀、quicker 的拖拽他们看不懂,看似是图形化界面,但其实需要很深的代码功底才能把它写出来,于是我想了一个点子,能不能让用户聊聊天就能把自动化搞出来,不需要拖拽也不需要了解参数(´・_・`),这些工作全部交给 AI 来做,AI 询问用户的需求,然后再根据用户的需求生成代码,用户可以直接对代码进行修改,不用担心看不懂,因为每一行代码都有友好的提示,而且在代码的旁边有一个小按钮,点击它就能运行,这意味着你无需下载/配置开发环境(¬‿¬)👉 ALL IN ONE,如果运行出错了,别担心,AI 会帮你排查并解决错误。最近 claude 推出了类似的功能,跟 AI 聊聊天就能生成一个贪吃蛇游戏项目,autoMate 和 claude 是否相同呢?不相同,我们只生成自动化代码,只会专注于自动化领域,因此我们的自动化生态会比 claude 更完善,不会生成游戏也不会生成工程项目。微软和苹果相继推出自动化产品 copilot plus 和 apple intelligence,号称能够自动化控制应用,如果他们真能实现, autoMate 还有价值吗?有价值,没有一个产品能满足所有人群的需求,就像 openai 没能统治所有大模型一样,微软和苹果也不可能吃掉所有市场,举一个具体场景,如果他们敢对微信做自动化,腾讯就有十足的把握送他们上法庭,但是我们的产品可以通过插件的方式控制微信。
|
||||
## ✨ 特性
|
||||
|
||||
## autoMate 是如何控制桌面应用的?
|
||||
- 🌈 聊聊天就能生成自动化代码。
|
||||
- 🔍 快捷键呼出搜索框一键运行自动化代码。
|
||||
- 📦 开箱即用的自动化工具套件。
|
||||
- ⚙️ 自动化开发框架和工具配套。
|
||||
- 🥳 兼容所有在线和本地大模型。
|
||||
|
||||
不得不说,这是一个世界级的难题。我想到了两个思路,一是封装和调用第三方函数库,二是利用视觉 AI(施工中) 。
|
||||
第一个思路很简单,大牛们已经封装好了常见的软件操作库,比如使用 python-docx 可以对 word 文档进行读写、使用 Selenium 可以点击浏览器页面中的元素,如果我的操作很复杂没有第三方库怎么办?比如修改 word 中所有表格,每个表格的 2 行 3 列数据精简至 10 字。这其中涉及的操作包括:
|
||||
1. 打开 word
|
||||
2. 读取所有表格的 2 行 3 列数据
|
||||
3. 利用 AI 精简数据
|
||||
4. 写入表格。
|
||||
## 🖥 环境支持
|
||||
|
||||
python-docx 能实现步骤 1、2、4,但是步骤 3 怎么实现呢?步骤 3 使用AI精简数据,没有这样的第三方库,这是定制的需求,于是我设想开发一个插件系统,用户只需要按照规则编写插插件并且上传,AI 就能够学会你上传的插件,对于步骤4 来说,我们可以写一个插件去调用大模型接口,让模型返回精简的 100字数据,将这个插件上传到 autoMate 后,智子就能够结合1~4个基础能力,生成可执行的自动化序列。
|
||||
即便是能够自己封装函数, 也不可能控制所有的软件呀?比如公司内部开发的软件,就没有任何函数库可以去控制,还有一些复杂操作,从网上搜集数据并且进行整理,开发这些插件的时间还不如人工去做!这就提到第二个解决方案:视觉AI算法。用户的自动化需求是打开A软件,点击红色按钮,用户只需要为软件A和红色按键截图然后打上标,于是自动化可以这么去做:
|
||||
- openai 的 api 格式大模型
|
||||
- 详见以下 litellm 配置
|
||||
|
||||
1. 利用 opencv 找到A软件,控制鼠标移动并左键点击;
|
||||
2. 利用opencv找到红色按钮,并控制鼠标移动并左键点击。
|
||||
|
||||
软件这么多,让用户一个一个打标太麻烦了,有更好的思路吗?有!取得用户同意后,autoMate 会拿到用户的打标数据,再把这些数据喂给 yolo,我们训练一个通用视觉模型,他可以识别大部分的场景。然后 yolo 模型结合大语言模型完成自动化用例。
|
||||
| 大模型 | [Completion](https://docs.litellm.ai/docs/#basic-usage) | [Streaming](https://docs.litellm.ai/docs/completion/stream#streaming-responses) | [Async Completion](https://docs.litellm.ai/docs/completion/stream#async-completion) | [Async Streaming](https://docs.litellm.ai/docs/completion/stream#async-streaming) | [Async Embedding](https://docs.litellm.ai/docs/embedding/supported_embedding) | [Async Image Generation](https://docs.litellm.ai/docs/image_generation) |
|
||||
|-------------------------------------------------------------------------------------|---------------------------------------------------------|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|-------------------------------------------------------------------------------|-------------------------------------------------------------------------|
|
||||
| [openai](https://docs.litellm.ai/docs/providers/openai) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [azure](https://docs.litellm.ai/docs/providers/azure) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [aws - sagemaker](https://docs.litellm.ai/docs/providers/aws_sagemaker) | ✅ | ✅ | ✅ | ✅ | ✅ | |
|
||||
| [aws - bedrock](https://docs.litellm.ai/docs/providers/bedrock) | ✅ | ✅ | ✅ | ✅ | ✅ | |
|
||||
| [google - vertex_ai](https://docs.litellm.ai/docs/providers/vertex) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [google - palm](https://docs.litellm.ai/docs/providers/palm) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [google AI Studio - gemini](https://docs.litellm.ai/docs/providers/gemini) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [mistral ai api](https://docs.litellm.ai/docs/providers/mistral) | ✅ | ✅ | ✅ | ✅ | ✅ | |
|
||||
| [cloudflare AI Workers](https://docs.litellm.ai/docs/providers/cloudflare_workers) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [cohere](https://docs.litellm.ai/docs/providers/cohere) | ✅ | ✅ | ✅ | ✅ | ✅ | |
|
||||
| [anthropic](https://docs.litellm.ai/docs/providers/anthropic) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [huggingface](https://docs.litellm.ai/docs/providers/huggingface) | ✅ | ✅ | ✅ | ✅ | ✅ | |
|
||||
| [replicate](https://docs.litellm.ai/docs/providers/replicate) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [together_ai](https://docs.litellm.ai/docs/providers/togetherai) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [openrouter](https://docs.litellm.ai/docs/providers/openrouter) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [ai21](https://docs.litellm.ai/docs/providers/ai21) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [baseten](https://docs.litellm.ai/docs/providers/baseten) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [vllm](https://docs.litellm.ai/docs/providers/vllm) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [nlp_cloud](https://docs.litellm.ai/docs/providers/nlp_cloud) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [aleph alpha](https://docs.litellm.ai/docs/providers/aleph_alpha) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [petals](https://docs.litellm.ai/docs/providers/petals) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [ollama](https://docs.litellm.ai/docs/providers/ollama) | ✅ | ✅ | ✅ | ✅ | ✅ | |
|
||||
| [deepinfra](https://docs.litellm.ai/docs/providers/deepinfra) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [perplexity-ai](https://docs.litellm.ai/docs/providers/perplexity) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [Groq AI](https://docs.litellm.ai/docs/providers/groq) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [Deepseek](https://docs.litellm.ai/docs/providers/deepseek) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [anyscale](https://docs.litellm.ai/docs/providers/anyscale) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
| [IBM - watsonx.ai](https://docs.litellm.ai/docs/providers/watsonx) | ✅ | ✅ | ✅ | ✅ | ✅ | |
|
||||
| [voyage ai](https://docs.litellm.ai/docs/providers/voyage) | | | | | ✅ | |
|
||||
| [xinference [Xorbits Inference]](https://docs.litellm.ai/docs/providers/xinference) | | | | | ✅ | |
|
||||
| [FriendliAI](https://docs.litellm.ai/docs/providers/friendliai) | ✅ | ✅ | ✅ | ✅ | | |
|
||||
|
||||
|
||||
## 功能介绍
|
||||
## 🔗 链接
|
||||
|
||||
### 基础功能
|
||||
|
||||
[详见--基础功能](https://s0soyusc93k.feishu.cn/wiki/JhhIwAUXJiBHG9kmt3YcXisWnec#O9W8dEqfBo13oQxCslycFUWonFd)
|
||||
- [基础功能](https://s0soyusc93k.feishu.cn/wiki/JhhIwAUXJiBHG9kmt3YcXisWnec#O9W8dEqfBo13oQxCslycFUWonFd)
|
||||
|
||||
## 开发指南
|
||||
- [项目理念](https://s0soyusc93k.feishu.cn/wiki/SR9ywLMZmin7gakGo21cnyaFnRf?from=from_copylink)
|
||||
|
||||
## 🍬 快速开始
|
||||
|
||||
下载 release 最新版本,双击即可直接运行,无需安装任何依赖。
|
||||
|
||||
## ⌨️ 本地开发
|
||||
|
||||
本项目分为前端和后端两个部分,前端项目在 app 目录下,后端项目在 server 目录下。这意味着,如果要运行 autoMate,你就得同时启动前端和后端。项目启动后会在~ 目录创建 sqlite 数据库 autoMate.db ,如果想查看数据库内容,建议使用开源数据库软件dbeaver。
|
||||
|
||||
@ -79,57 +108,16 @@ python-docx 能实现步骤 1、2、4,但是步骤 3 怎么实现呢?步骤
|
||||
|
||||
前端打包命令:
|
||||
|
||||
# win可以换成mac
|
||||
|
||||
`npm run build:win`
|
||||
|
||||
打包完成后,将main.exe放在前端根目录下。
|
||||
|
||||
### 开发小技巧
|
||||
## 🤝 参与共建
|
||||
|
||||
如果你想用console.log打印内容并且查看,需要打开devtools才能够查看到,可以在/app/src/main/windows.ts中将openDevTools工具设置为true。每一个界面都有单独的devtools。根据自己调试需要打开相应的开关即可。
|
||||
|
||||
```js
|
||||
export const config = {
|
||||
search: {
|
||||
id: 0,
|
||||
options: {
|
||||
initShow: true,
|
||||
hash: '',
|
||||
openDevTools: false,
|
||||
}
|
||||
},
|
||||
code: {
|
||||
id: 0,
|
||||
options: {
|
||||
initShow: true,
|
||||
width: 1300,
|
||||
height: 700,
|
||||
openDevTools: false,
|
||||
frame: true,
|
||||
transparent: false,
|
||||
hash: '/#config/category/contentList'
|
||||
}
|
||||
},
|
||||
config: {
|
||||
id: 0,
|
||||
options: {
|
||||
initShow: true,
|
||||
width: 600,
|
||||
height: 400,
|
||||
openDevTools: false,
|
||||
frame: true,
|
||||
transparent: false,
|
||||
hash: '/#config'
|
||||
}
|
||||
}
|
||||
|
||||
} as Record<WindowNameType, {id: number, options: OptionsType }>
|
||||
```
|
||||
|
||||
## 感谢以下贡献者
|
||||
请参考[贡献指南](https://s0soyusc93k.feishu.cn/wiki/ZE7KwtRweicLbNkHSdMcBMTxngg?from=from_copylink).
|
||||
|
||||
> 强烈推荐阅读 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way)、[《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545) 和 [《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/%7Esgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393),更好的问题更容易获得帮助。
|
||||
|
||||
<a href="https://github.com/yuruotong1/autoMate/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=yuruotong1/autoMate" />
|
||||
</a>
|
||||
</a>
|
||||
|
||||
@ -39,7 +39,7 @@ function initData() {
|
||||
const initData = findOne('select * from config')
|
||||
if (initData) return
|
||||
const llm = {model: "gpt-4-turbo", api_key: "", base_url: "https://api.openai.com/v1"}
|
||||
db().exec(`insert into config (id, content) values(1,'{"shortCut":"Alt+d","llm": ${JSON.stringify(llm)}}')`)
|
||||
db().exec(`insert into config (id, content) values(1,'{"shortcut":"Alt+d","llm": ${JSON.stringify(llm)}}')`)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import { electronApp, optimizer } from '@electron-toolkit/utils'
|
||||
import "./db"
|
||||
import "./windows"
|
||||
import "./ipc"
|
||||
import "./shortCut"
|
||||
import "./shortcut"
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
|
||||
@ -4,26 +4,24 @@ import { getWindowByName } from "./windows"
|
||||
import { findOne } from "./db/query"
|
||||
const { app, globalShortcut } = require('electron')
|
||||
|
||||
ipcMain.handle("shortCut", (_event: IpcMainInvokeEvent) => {
|
||||
ipcMain.handle("shortcut", (_event: IpcMainInvokeEvent) => {
|
||||
// react 严格模式会执行两次,可能会导致快捷键重复注册,这里在注册前会删除旧快捷键
|
||||
return registerSearchShortCut()
|
||||
return registerSearchShortcut()
|
||||
|
||||
})
|
||||
|
||||
|
||||
export function registerSearchShortCut(){
|
||||
export function registerSearchShortcut(){
|
||||
globalShortcut.unregisterAll()
|
||||
const ret = findOne(`select * from config where id=1`) as {content: string}
|
||||
console.log(ret)
|
||||
const shortCut = JSON.parse(ret.content).shortcut as string
|
||||
console.log(shortCut)
|
||||
if (shortCut && globalShortcut.isRegistered(shortCut)){
|
||||
const shortcut = JSON.parse(ret.content).shortcut as string
|
||||
if (shortcut && globalShortcut.isRegistered(shortcut)){
|
||||
dialog.showErrorBox('提示', '快捷键注册失败,请更换')
|
||||
return false
|
||||
}
|
||||
|
||||
const win = getWindowByName('search')
|
||||
const res = globalShortcut.register(shortCut, () => {
|
||||
const res = globalShortcut.register(shortcut, () => {
|
||||
win.isVisible() ? win.hide() : win.show()
|
||||
})
|
||||
return res
|
||||
|
||||
@ -27,9 +27,9 @@ export const config = {
|
||||
id: 0,
|
||||
options: {
|
||||
initShow: true,
|
||||
width: 800,
|
||||
height: 650,
|
||||
openDevTools: true,
|
||||
width: 830,
|
||||
height: 670,
|
||||
openDevTools: false,
|
||||
frame: true,
|
||||
transparent: false,
|
||||
hash: '/#config'
|
||||
|
||||
2
app/src/preload/index.d.ts
vendored
2
app/src/preload/index.d.ts
vendored
@ -5,7 +5,7 @@ declare global {
|
||||
interface Window {
|
||||
electron: ElectronAPI
|
||||
api: {
|
||||
shortCut: () => Promise<boolean>,
|
||||
shortcut: () => Promise<boolean>,
|
||||
setIgnoreMouseEvents: (ignore: boolean, options?: { forward: boolean }) => void,
|
||||
openConfigWindow: () => void,
|
||||
sql: <T>(sql: string, type: SqlActionType, params?: Record<string, any>) => Promise<T>
|
||||
|
||||
@ -3,8 +3,8 @@ import { electronAPI } from '@electron-toolkit/preload'
|
||||
|
||||
// Custom APIs for renderer
|
||||
const api = {
|
||||
shortCut: () => {
|
||||
return ipcRenderer.invoke("shortCut")
|
||||
shortcut: () => {
|
||||
return ipcRenderer.invoke("shortcut")
|
||||
},
|
||||
setIgnoreMouseEvents: (ignore: boolean, options?: { forward: boolean }) => {
|
||||
ipcRenderer.send("setIgnoreMouseEvents", ignore, options)
|
||||
|
||||
@ -12,8 +12,8 @@ function App(): JSX.Element {
|
||||
useEffect(()=>{
|
||||
setIgnoreMouseEvents(mainRef as MutableRefObject<HTMLDivElement>)
|
||||
}, [])
|
||||
// const shortCut = useShortCut("CommandOrControl+n")
|
||||
// shortCut.register()
|
||||
// const shortcut = useShortCut("CommandOrControl+n")
|
||||
// shortcut.register()
|
||||
return (
|
||||
<CodeProvider>
|
||||
<main className="relative" ref={mainRef}>
|
||||
|
||||
@ -7,9 +7,9 @@ export default() => {
|
||||
})
|
||||
|
||||
document.body.addEventListener('mouseover', (e: MouseEvent)=>{
|
||||
// if (e.target === document.body) {
|
||||
// window.api.setIgnoreMouseEvents(true, {forward: true})
|
||||
// }
|
||||
if (e.target === document.body) {
|
||||
window.api.setIgnoreMouseEvents(true, {forward: true})
|
||||
}
|
||||
})
|
||||
}
|
||||
return {setIgnoreMouseEvents}
|
||||
|
||||
@ -4,7 +4,7 @@ export default() => {
|
||||
// const setError = useStore(state => state.setError)
|
||||
// const register = async ()=>{
|
||||
// const ret = (await window.api.sql('', 'config')) as Record<string, string>
|
||||
// const isBind = await window.api.shortCut(ret.shortCut)
|
||||
// const isBind = await window.api.shortcut(ret.shortcut)
|
||||
// isBind || setError("注册失败")
|
||||
|
||||
// }
|
||||
|
||||
@ -12,7 +12,7 @@ function Home(): JSX.Element {
|
||||
const {setIgnoreMouseEvents} = useIgnoreMouseEvents()
|
||||
window.api.initTable()
|
||||
// 注册快捷键
|
||||
window.api.shortCut()
|
||||
window.api.shortcut()
|
||||
const setError = useStore((state)=>state.setError)
|
||||
useEffect(()=>{
|
||||
setIgnoreMouseEvents(mainRef as MutableRefObject<HTMLDivElement>)
|
||||
|
||||
@ -35,7 +35,6 @@ export const Content = () => {
|
||||
</select>
|
||||
<Button onClick={async () => {
|
||||
const code_content = (await window.api.sql(`select * from contents where id = ${content.id}`, "findOne")) as ContentType
|
||||
console.log(code_content)
|
||||
const res = await fetch(localServerBaseUrl + "/execute", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
@ -1,170 +1,173 @@
|
||||
import { Button, Form, Input, Select, Space, message } from 'antd';
|
||||
import styles from './styles.module.scss'
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useLoaderData } from 'react-router-dom';
|
||||
import { localServerBaseUrl } from '@renderer/config';
|
||||
|
||||
export function Setting() {
|
||||
export function Setting(){
|
||||
const [form] = Form.useForm();
|
||||
const config = useLoaderData() as ConfigDataType
|
||||
form.setFieldsValue(config);
|
||||
useEffect(() => {
|
||||
form.setFieldsValue(config);
|
||||
}, [])
|
||||
const { Option } = Select;
|
||||
const tailLayout = {
|
||||
wrapperCol: { offset: 6, span: 16 },
|
||||
};
|
||||
const [keys, setKeys] = useState<string[]>([])
|
||||
const onModelChange = (value: string) => {
|
||||
switch (value) {
|
||||
case 'openai':
|
||||
form.setFieldsValue({ model: 'gpt-4-turbo', base_url: "https://api.openai.com/v1" });
|
||||
break;
|
||||
case 'ollama':
|
||||
form.setFieldsValue({ model: 'ollama/codeqwen:latest', api_base: "http://localhost:11434" });
|
||||
break;
|
||||
default:
|
||||
}
|
||||
wrapperCol: { offset: 6, span: 16 },
|
||||
};
|
||||
|
||||
const onFinish = async (values: any) => {
|
||||
// 注册快捷键
|
||||
window.api.shortCut()
|
||||
await window.api.sql(`update config set content=@content where id = 1`,
|
||||
'update',
|
||||
{
|
||||
content: JSON.stringify(values)
|
||||
})
|
||||
// window.close();
|
||||
};
|
||||
const [keys, setKeys] = useState<string[]>([])
|
||||
const onModelChange = (value: string) => {
|
||||
switch (value) {
|
||||
case 'openai':
|
||||
form.resetFields(['llm'])
|
||||
form.setFieldsValue({"llm": {model: 'gpt-4-turbo', base_url:"https://api.openai.com/v1"}});
|
||||
break;
|
||||
case 'ollama':
|
||||
form.resetFields(['llm'])
|
||||
form.setFieldsValue({"llm": {model: 'ollama/codeqwen:latest',api_base: "http://localhost:11434" }});
|
||||
break;
|
||||
default:
|
||||
}
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
window.close()
|
||||
};
|
||||
const onFinish = async (values: any) => {
|
||||
await window.api.sql(`update config set content=@content where id = 1`,
|
||||
'update',
|
||||
{
|
||||
content: JSON.stringify(values)
|
||||
})
|
||||
// 注册快捷键
|
||||
window.api.shortcut()
|
||||
window.close();
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
window.close()
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.settingPage}>
|
||||
<h1>Setting</h1>
|
||||
<Form
|
||||
form={form}
|
||||
name="control-hooks"
|
||||
onFinish={onFinish}
|
||||
>
|
||||
<section>
|
||||
<h5>快捷键定义</h5>
|
||||
<Form.Item name="shortcut" label="呼出快捷键" rules={[{ required: true }]}>
|
||||
<Input
|
||||
type="text"
|
||||
readOnly
|
||||
onKeyDown={(e) => {
|
||||
if (e.metaKey || e.ctrlKey || e.altKey) {
|
||||
const code = e.code.replace(/Left|Right|Key|Digit/, '')
|
||||
if (keys.includes(code)) return
|
||||
keys.push(code)
|
||||
setKeys(keys)
|
||||
// 如果以数字或字母或者空格结尾
|
||||
if (code.match(/^(\w|\s)$/gi)) {
|
||||
e.currentTarget.value = keys.join('+')
|
||||
form.setFieldsValue({ shortcut: e.currentTarget.value })
|
||||
setKeys([])
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</section>
|
||||
<section>
|
||||
|
||||
<div className='flex flex-row justify-between items-center mb-3'>
|
||||
<h5>大模型配置信息</h5>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const hide = message.loading('检测中...', 0);
|
||||
try {
|
||||
const res = await fetch(`${localServerBaseUrl}/llm`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(
|
||||
{
|
||||
"messages": [{ "role": "user", "content": "hello" }],
|
||||
"llm_config": JSON.stringify(form.getFieldValue("llm"))
|
||||
}),
|
||||
}
|
||||
)
|
||||
hide();
|
||||
if (res.ok) {
|
||||
message.success('连接成功')
|
||||
} else {
|
||||
message.error(`连接失败,请检查配置信息是否正确:\n${res.statusText}`)
|
||||
}
|
||||
} catch (error) {
|
||||
hide();
|
||||
message.error(`连接失败,请检查配置信息是否正确:\n${error}`)
|
||||
}
|
||||
}}>检查连接</Button>
|
||||
</div>
|
||||
<Form.Item name="format" label="格式" rules={[{ required: true }]}>
|
||||
<Select
|
||||
placeholder="选择格式,一般OpenAI格式为万能格式"
|
||||
onChange={onModelChange}
|
||||
allowClear
|
||||
>
|
||||
<Option value="openai">OpenAI</Option>
|
||||
<Option value="ollama">Ollama</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name={["llm", "model"]} label="model" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) => prevValues.format !== currentValues.format}
|
||||
>
|
||||
{({ getFieldValue }) =>
|
||||
getFieldValue('format') === 'openai' ? (
|
||||
<div>
|
||||
<Form.Item name={["llm", "api_key"]} label="api_key" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={["llm", "base_url"]} label="base_url" rules={[{ required: false }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) => prevValues.format !== currentValues.format}
|
||||
>
|
||||
{({ getFieldValue }) =>
|
||||
getFieldValue('format') === 'ollama' ? (
|
||||
<Form.Item name={["llm", "api_base"]} label="api_base" rules={[{ required: false }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
) : null
|
||||
}
|
||||
</Form.Item>
|
||||
</section>
|
||||
<div className='mt-10 flex justify-center items-center'>
|
||||
<Form.Item {...tailLayout} >
|
||||
<Space>
|
||||
<Button htmlType="button" onClick={onClose} className='mr-5'>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit" >
|
||||
保存
|
||||
</Button>
|
||||
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form>
|
||||
return (
|
||||
<div className={styles.settingPage}>
|
||||
<h1>Setting</h1>
|
||||
<Form
|
||||
form={form}
|
||||
name="control-hooks"
|
||||
onFinish={onFinish}
|
||||
>
|
||||
<section>
|
||||
<h5>快捷键定义</h5>
|
||||
<Form.Item name="shortcut" label="呼出快捷键" rules={[{ required: true }]}>
|
||||
<Input
|
||||
type="text"
|
||||
readOnly
|
||||
onKeyDown={(e) => {
|
||||
if (e.metaKey || e.ctrlKey || e.altKey) {
|
||||
const code = e.code.replace(/Left|Right|Key|Digit/, '')
|
||||
if (keys.includes(code)) return
|
||||
keys.push(code)
|
||||
setKeys(keys)
|
||||
// 如果以数字或字母或者空格结尾
|
||||
if (code.match(/^(\w|\s)$/gi)) {
|
||||
e.currentTarget.value = keys.join('+')
|
||||
form.setFieldsValue({shortcut: e.currentTarget.value})
|
||||
setKeys([])
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</section>
|
||||
<section>
|
||||
|
||||
<div className='flex flex-row justify-between items-center mb-3'>
|
||||
<h5>大模型配置信息</h5>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const hide = message.loading('检测中...', 0);
|
||||
try {
|
||||
const res = await fetch(`${localServerBaseUrl}/llm`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(
|
||||
{ "messages": [{ "role": "user", "content": "hello" }],
|
||||
"llm_config": JSON.stringify(form.getFieldValue("llm"))}),
|
||||
}
|
||||
)
|
||||
hide();
|
||||
if (res.ok) {
|
||||
message.success('连接成功')
|
||||
} else {
|
||||
message.error(`连接失败,请检查配置信息是否正确:\n${res.statusText}`)
|
||||
}
|
||||
} catch (error) {
|
||||
hide();
|
||||
message.error(`连接失败,请检查配置信息是否正确:\n${error}`)
|
||||
}
|
||||
}}>检查连接</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
<Form.Item name="format" label="格式" rules={[{ required: true }]}>
|
||||
<Select
|
||||
placeholder="选择格式,一般OpenAI格式为万能格式"
|
||||
onChange={onModelChange}
|
||||
allowClear
|
||||
>
|
||||
<Option value="openai">OpenAI</Option>
|
||||
<Option value="ollama">Ollama</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name={["llm", "model"]} label="model" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) => prevValues.format !== currentValues.format}
|
||||
>
|
||||
{({ getFieldValue }) =>
|
||||
getFieldValue('format') === 'openai' ? (
|
||||
<div>
|
||||
<Form.Item name={["llm", "api_key"]} label="api_key" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={["llm", "base_url"]} label="base_url" rules={[{ required: false }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) => prevValues.format !== currentValues.format}
|
||||
>
|
||||
{({ getFieldValue }) =>
|
||||
getFieldValue('format') === 'ollama' ? (
|
||||
<Form.Item name={["llm", "api_base"]} label="api_base" rules={[{ required: false }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
) : null
|
||||
}
|
||||
</Form.Item>
|
||||
</section>
|
||||
<div className='mt-10 flex justify-center items-center'>
|
||||
<Form.Item {...tailLayout} >
|
||||
<Space>
|
||||
<Button htmlType="button" onClick={onClose} className='mr-5'>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit" >
|
||||
保存
|
||||
</Button>
|
||||
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -16,8 +16,8 @@ export const Setting = () => {
|
||||
<h5>快捷键定义</h5>
|
||||
<input
|
||||
type="text"
|
||||
name="shortCut"
|
||||
defaultValue={config.shortCut}
|
||||
name="shortcut"
|
||||
defaultValue={config.shortcut}
|
||||
readOnly
|
||||
onKeyDown={(e) => {
|
||||
if (e.metaKey || e.ctrlKey || e.altKey) {
|
||||
@ -29,9 +29,9 @@ export const Setting = () => {
|
||||
if (code.match(/^(\w|\s)$/gi)) {
|
||||
e.currentTarget.value = keys.join('+')
|
||||
setKeys([])
|
||||
config.shortCut = e.currentTarget.value
|
||||
config.shortcut = e.currentTarget.value
|
||||
// 注册快捷键
|
||||
window.api.shortCut()
|
||||
window.api.shortcut()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
app/types.d.ts
vendored
2
app/types.d.ts
vendored
@ -24,7 +24,7 @@ type ConfigType = {
|
||||
|
||||
|
||||
type ConfigDataType = {
|
||||
shortCut: string
|
||||
shortcut: string
|
||||
format: string
|
||||
llm: {
|
||||
model: string
|
||||
|
||||
14
package.bat
14
package.bat
@ -1,20 +1,18 @@
|
||||
cd app
|
||||
|
||||
set "target_dir=.\dist"
|
||||
del /f /q "%target_dir%\*.*"
|
||||
for /d %%i in ("%target_dir%\*") do rmdir /s /q "%%i"
|
||||
|
||||
call npm run build:win
|
||||
move .\dist\win-unpacked\resources\app.asar.unpacked\resources\icon.png .\dist\win-unpacked\resources
|
||||
cd ..
|
||||
cd server
|
||||
call .\.venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
call echo y | pyinstaller main.spec
|
||||
xcopy .\.venv\Lib\site-packages\litellm\*.json .\dist\autoMateServer\_internal\litellm\ /E /H /F /I /Y
|
||||
|
||||
|
||||
REM 设置要清空的目录路径
|
||||
set "target_dir=..\app\dist"
|
||||
REM 删除目标目录中的所有文件
|
||||
del /f /q "%target_dir%\*.*"
|
||||
REM 删除目标目录中的所有子目录
|
||||
for /d %%i in ("%target_dir%\*") do rmdir /s /q
|
||||
|
||||
xcopy .\dist\autoMateServer\* ..\app\dist\win-unpacked\ /E /H /F /I /Y
|
||||
cd ..\app\dist
|
||||
REN win-unpacked autoMate
|
||||
|
||||
BIN
resources/autoMate.png
Normal file
BIN
resources/autoMate.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Loading…
x
Reference in New Issue
Block a user