diff --git a/README.md b/README.md index 4316ded..0d2ceec 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,87 @@ -
+
-autoMate logo +autoMate logo +

autoMate

-
autoMate
+一个 Agent 和 RPA 开发平台 -
-autoMate 就像生活出行中的共享单车,帮你完成软件的最后一个操作,只需聊聊天就能将 AI 植入任意一个软件。 - -
+[![][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) +![](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png) + +[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 +
-autoMate 可以生成自动化代码帮助用户减少复性劳动,他做了三件事: - -1. 了解、分析用户需求,将需求转换为人人都能看懂的用例; -2. 将用例转换成可执行的代码; -3. 运行代码并调试,解决异常问题。 - -各产品优势对比如下。 - -![alt text](./resources/diff.png) +[![](./resources/autoMate.png)](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 -``` - -## 感谢以下贡献者 +请参考[贡献指南](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),更好的问题更容易获得帮助。 - \ No newline at end of file + diff --git a/app/src/main/db/tables.ts b/app/src/main/db/tables.ts index feaae22..b6afac3 100644 --- a/app/src/main/db/tables.ts +++ b/app/src/main/db/tables.ts @@ -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)}}')`) } diff --git a/app/src/main/index.ts b/app/src/main/index.ts index 9ceef51..c4e22ea 100644 --- a/app/src/main/index.ts +++ b/app/src/main/index.ts @@ -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. diff --git a/app/src/main/shortCut.ts b/app/src/main/shortCut.ts index fdd2945..c9f0573 100644 --- a/app/src/main/shortCut.ts +++ b/app/src/main/shortCut.ts @@ -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 diff --git a/app/src/main/windows.ts b/app/src/main/windows.ts index b153958..951c1e6 100644 --- a/app/src/main/windows.ts +++ b/app/src/main/windows.ts @@ -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' diff --git a/app/src/preload/index.d.ts b/app/src/preload/index.d.ts index 79740b6..805c1d5 100644 --- a/app/src/preload/index.d.ts +++ b/app/src/preload/index.d.ts @@ -5,7 +5,7 @@ declare global { interface Window { electron: ElectronAPI api: { - shortCut: () => Promise, + shortcut: () => Promise, setIgnoreMouseEvents: (ignore: boolean, options?: { forward: boolean }) => void, openConfigWindow: () => void, sql: (sql: string, type: SqlActionType, params?: Record) => Promise diff --git a/app/src/preload/index.ts b/app/src/preload/index.ts index b5bb287..268d18c 100644 --- a/app/src/preload/index.ts +++ b/app/src/preload/index.ts @@ -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) diff --git a/app/src/renderer/src/App.tsx b/app/src/renderer/src/App.tsx index 0d63a66..c724280 100644 --- a/app/src/renderer/src/App.tsx +++ b/app/src/renderer/src/App.tsx @@ -12,8 +12,8 @@ function App(): JSX.Element { useEffect(()=>{ setIgnoreMouseEvents(mainRef as MutableRefObject) }, []) - // const shortCut = useShortCut("CommandOrControl+n") - // shortCut.register() + // const shortcut = useShortCut("CommandOrControl+n") + // shortcut.register() return (
diff --git a/app/src/renderer/src/hooks/useIgnoreMouseEvents.ts b/app/src/renderer/src/hooks/useIgnoreMouseEvents.ts index 81d63dc..4fa19f9 100644 --- a/app/src/renderer/src/hooks/useIgnoreMouseEvents.ts +++ b/app/src/renderer/src/hooks/useIgnoreMouseEvents.ts @@ -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} diff --git a/app/src/renderer/src/hooks/useShortCut.ts b/app/src/renderer/src/hooks/useShortCut.ts index fabc52b..7f09be7 100644 --- a/app/src/renderer/src/hooks/useShortCut.ts +++ b/app/src/renderer/src/hooks/useShortCut.ts @@ -4,7 +4,7 @@ export default() => { // const setError = useStore(state => state.setError) // const register = async ()=>{ // const ret = (await window.api.sql('', 'config')) as Record - // const isBind = await window.api.shortCut(ret.shortCut) + // const isBind = await window.api.shortcut(ret.shortcut) // isBind || setError("注册失败") // } diff --git a/app/src/renderer/src/layouts/Home/index.tsx b/app/src/renderer/src/layouts/Home/index.tsx index 3facaa7..07aa9a2 100644 --- a/app/src/renderer/src/layouts/Home/index.tsx +++ b/app/src/renderer/src/layouts/Home/index.tsx @@ -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) diff --git a/app/src/renderer/src/pages/Content/Content.tsx b/app/src/renderer/src/pages/Content/Content.tsx index f253307..4774974 100644 --- a/app/src/renderer/src/pages/Content/Content.tsx +++ b/app/src/renderer/src/pages/Content/Content.tsx @@ -35,7 +35,6 @@ export const Content = () => { -
- - - - - - - - - prevValues.format !== currentValues.format} - > - {({ getFieldValue }) => - getFieldValue('format') === 'openai' ? ( -
- - - - - - -
- ) : null - } -
- - prevValues.format !== currentValues.format} - > - {({ getFieldValue }) => - getFieldValue('format') === 'ollama' ? ( - - - - ) : null - } - - -
- - - - - - - -
- + return ( +
+

Setting

+
+
+
快捷键定义
+ + { + 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([]) + } + } + }} + /> + +
+
+ +
+
大模型配置信息
+
- ); -}; + + + + + + + + + prevValues.format !== currentValues.format} + > + {({ getFieldValue }) => + getFieldValue('format') === 'openai' ? ( +
+ + + + + + +
+ ) : null + } +
+ + prevValues.format !== currentValues.format} + > + {({ getFieldValue }) => + getFieldValue('format') === 'ollama' ? ( + + + + ) : null + } + +
+
+ + + + + + + +
+
+
+ ); +}; \ No newline at end of file diff --git a/app/src/renderer/src/pages/Setting/index.tsx.bk b/app/src/renderer/src/pages/Setting/index.tsx.bk index d15d022..913d0a4 100644 --- a/app/src/renderer/src/pages/Setting/index.tsx.bk +++ b/app/src/renderer/src/pages/Setting/index.tsx.bk @@ -16,8 +16,8 @@ export const Setting = () => {
快捷键定义
{ 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() } } diff --git a/app/types.d.ts b/app/types.d.ts index 32ef97b..f143d7f 100644 --- a/app/types.d.ts +++ b/app/types.d.ts @@ -24,7 +24,7 @@ type ConfigType = { type ConfigDataType = { - shortCut: string + shortcut: string format: string llm: { model: string diff --git a/package.bat b/package.bat index 516a05a..2a5048a 100644 --- a/package.bat +++ b/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 diff --git a/resources/autoMate.png b/resources/autoMate.png new file mode 100644 index 0000000..0398b8c Binary files /dev/null and b/resources/autoMate.png differ