diff --git a/.gitignore b/.gitignore index 0e7a59b..d7593d6 100644 --- a/.gitignore +++ b/.gitignore @@ -181,3 +181,6 @@ workspace/ # Private Config config/config.toml + +# Desktop runtime +desktop/frontend/wailsjs/runtime/ diff --git a/desktop/app.go b/desktop/app.go index e796dcc..5ea6f46 100644 --- a/desktop/app.go +++ b/desktop/app.go @@ -4,6 +4,8 @@ import ( "OpenManus/src/utils" "context" "fmt" + + "github.com/wailsapp/wails/v2/pkg/runtime" ) // App struct @@ -26,6 +28,44 @@ func NewApp() *App { // so we can call the runtime methods func (a *App) startup(ctx context.Context) { a.ctx = ctx + + // 注册事件监听器 + runtime.EventsOn(ctx, "events", func(data ...interface{}) { + if len(data) > 0 { + for i := 0; i < len(data); i++ { + fmt.Println("Received events with data:", data[i]) + } + } else { + fmt.Println("Received events without data") + } + }) + + // 注册bat批处理事件监听器 + runtime.EventsOn(ctx, "bat", func(data ...interface{}) { + if len(data) == 2 { + fmt.Println("Received bat with data, batId: ", data[0]) + fmt.Println("Received bat with data, batPath: ", data[1]) + utils.ExecBatFile(a.ctx, data[0].(string), data[1].(string)) + } else if len(data) > 0 && len(data) < 2 { + fmt.Println("Received bat with data, required 2 paramters, found 1: ", data[0]) + } else { + fmt.Println("Received bat without data") + } + }) + + // 注册执行py脚本监听器 + runtime.EventsOn(ctx, "pyFile", func(data ...interface{}) { + if len(data) == 2 { + fmt.Println("Received bat with data, batId: ", data[0]) + fmt.Println("Received bat with data, batPath: ", data[1]) + utils.ExecPyFile(a.ctx, data[0].(string), data[1].(string)) + } else if len(data) > 0 && len(data) < 2 { + fmt.Println("Received bat with data, required 2 paramters, found 1: ", data[0]) + } else { + fmt.Println("Received bat without data") + } + }) + } // Greet returns a greeting for the given name @@ -35,8 +75,33 @@ func (a *App) Greet(name string) string { // ReadAll reads file content func (a *App) ReadAll(filePath string) string { + utils.Log("ReadAll filePath: ", filePath) // Read the file content, resulting in a JSON string containing file content and callback ID data := string(utils.ReadAll(filePath)) utils.Log("ReadAll data: ", data) return data } + +func (a *App) SaveFile(filePath string, data string) { + utils.SaveFile(filePath, data) +} + +func (a *App) PathExists(path string) bool { + exists, _ := utils.PathExists(path) + return exists +} + +func (a *App) DirSize(path string) int64 { + utils.Log("DirSize path: ", path) + size, _ := utils.DirSize(path) + utils.Log("DirSize size: ", size) + return size +} + +func (a *App) AppPath() string { + return utils.AppPath() +} + +func (a *App) CheckPort(port string) bool { + return utils.CheckPort(port) +} diff --git a/desktop/frontend/index.html b/desktop/frontend/index.html index b6c943e..7967e84 100644 --- a/desktop/frontend/index.html +++ b/desktop/frontend/index.html @@ -1,12 +1,15 @@ + - - - OpenManus + + + OpenManus + -
- +
+ + diff --git a/desktop/frontend/package-lock.json b/desktop/frontend/package-lock.json index fd22629..551cace 100644 --- a/desktop/frontend/package-lock.json +++ b/desktop/frontend/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "axios": "^1.8.3", "element-plus": "^2.9.2", + "marked": "^15.0.7", "pinia": "^3.0.1", "pinia-plugin-persistedstate": "^4.2.0", "qs": "^6.14.0", @@ -2133,6 +2134,17 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/marked": { + "version": "15.0.7", + "resolved": "https://registry.npmmirror.com/marked/-/marked-15.0.7.tgz", + "integrity": "sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", diff --git a/desktop/frontend/package.json b/desktop/frontend/package.json index c5abc16..63db4fb 100644 --- a/desktop/frontend/package.json +++ b/desktop/frontend/package.json @@ -11,6 +11,7 @@ "dependencies": { "axios": "^1.8.3", "element-plus": "^2.9.2", + "marked": "^15.0.7", "pinia": "^3.0.1", "pinia-plugin-persistedstate": "^4.2.0", "qs": "^6.14.0", diff --git a/desktop/frontend/src/assets/css/main.css b/desktop/frontend/src/assets/css/main.css index 34dc7d4..c14d091 100644 --- a/desktop/frontend/src/assets/css/main.css +++ b/desktop/frontend/src/assets/css/main.css @@ -1,5 +1,5 @@ :root { - --el-menu-base-level-padding: 10px !important; + --el-menu-base-level-padding: 4px !important; /** Indents for submenu items */ --el-menu-level-padding: 20px !important; } @@ -186,12 +186,23 @@ img.edit:hover { } .main-content { + width: 100%; + padding: 0px 16px; + margin-top: 16px; + margin-bottom: 16px; +} + +.main-content-full-height { width: 100%; /** 44 + 16 + 16 */ height: calc(100vh - 76px); padding: 0px 16px; margin-top: 16px; margin-bottom: 16px; + display: flex; + flex-direction: column; + justify-content: center !important; + align-items: center; } /** Element Plus Start */ @@ -245,6 +256,10 @@ button.el-button--default { font-size: 14px; } +button.el-button--large { + font-size: 18px; +} + button>span.el-button__text--expand { font-size: 14px; } @@ -270,7 +285,7 @@ button>span.el-button__text--expand { /* If defined in AsideMenu, menu components associated may not read some overridden styles */ .el-sub-menu__title { - padding: 0px 10px !important; + padding: 0px 4px !important; border-radius: 6px; border: none !important; } @@ -349,6 +364,10 @@ div.el-divider { margin-bottom: auto !important; } +.el-step__description { + padding-right: 0px !important; +} + /** Element Plus End */ .block { @@ -444,6 +463,20 @@ div.el-divider { align-items: center; } +.fyc { + display: flex; + flex-direction: column; + justify-content: center !important; + align-items: center; +} + +.fys { + display: flex; + flex-direction: column; + justify-content: center !important; + align-items: start; +} + .fxsb { display: flex; justify-content: space-between !important; @@ -502,20 +535,33 @@ div.el-divider { flex-wrap: wrap; } -.header-logo, -.header-title { - padding: 4px 10px; -} - -.header-title h1 { - font-size: 16px; - font-weight: 300; +h1.el-text { + --el-text-font-size: 48px; letter-spacing: -0.5px } -.header-title h2 { +h2.el-text { + --el-text-font-size: 32px; + letter-spacing: -0.5px +} + +h3.el-text { + --el-text-font-size: 24px; + letter-spacing: -0.5px +} + +h4.el-text { --el-text-font-size: 20px; - font-weight: 500; + letter-spacing: -0.5px +} + +h5.el-text { + --el-text-font-size: 16px; + letter-spacing: -0.5px +} + +h6.el-text { + --el-text-font-size: 14px; letter-spacing: -0.5px } @@ -934,6 +980,30 @@ div.el-divider { margin-left: 12px !important; } +.mt--8 { + margin-top: -8px !important; +} + +.mt--10 { + margin-top: -10px !important; +} + +.mt--12 { + margin-top: -12px !important; +} + +.mt--16 { + margin-top: -16px !important; +} + +.mt--20 { + margin-top: -20px !important; +} + +.mt--24 { + margin-top: -24px !important; +} + .mt-0-5 { margin-top: 0.5px !important; } @@ -970,6 +1040,10 @@ div.el-divider { margin-top: 18px !important; } +.mt-20 { + margin-top: 20px !important; +} + .mr-8 { margin-right: 8px; } @@ -1061,6 +1135,11 @@ div.el-divider { margin-bottom: 18px !important; } +.mtb-20 { + margin-top: 20px !important; + margin-bottom: 20px !important; +} + .plr-4 { padding-left: 4px !important; padding-right: 4px !important; @@ -1280,6 +1359,10 @@ div.el-divider { overflow-x: auto; } +.min-h32 { + min-height: 32px !important; +} + .min-h46 { min-height: 46px !important; } @@ -1296,6 +1379,14 @@ div.el-divider { width: auto !important; } +.max-w-400 { + max-width: 400px !important; +} + +.max-w-500 { + max-width: 500px !important; +} + .max-w-640 { max-width: 640px !important; } @@ -1603,6 +1694,18 @@ div.el-divider { padding-left: 10px !important; } +.pl-12 { + padding-left: 12px !important; +} + +.pl-14 { + padding-left: 14px !important; +} + +.pl-16 { + padding-left: 16px !important; +} + .pl-20 { padding-left: 20px !important; } diff --git a/desktop/frontend/src/assets/img/logo-b-sm.png b/desktop/frontend/src/assets/img/logo-b-sm.png new file mode 100644 index 0000000..ea86bd4 Binary files /dev/null and b/desktop/frontend/src/assets/img/logo-b-sm.png differ diff --git a/desktop/frontend/src/assets/img/logo-b.png b/desktop/frontend/src/assets/img/logo-b.png new file mode 100644 index 0000000..61aacc0 Binary files /dev/null and b/desktop/frontend/src/assets/img/logo-b.png differ diff --git a/desktop/frontend/src/assets/img/logo-sm.png b/desktop/frontend/src/assets/img/logo-sm.png deleted file mode 100644 index d294102..0000000 Binary files a/desktop/frontend/src/assets/img/logo-sm.png and /dev/null differ diff --git a/desktop/frontend/src/assets/img/logo-w-sm.png b/desktop/frontend/src/assets/img/logo-w-sm.png new file mode 100644 index 0000000..5377bfc Binary files /dev/null and b/desktop/frontend/src/assets/img/logo-w-sm.png differ diff --git a/desktop/frontend/src/assets/img/logo-w.png b/desktop/frontend/src/assets/img/logo-w.png new file mode 100644 index 0000000..569259d Binary files /dev/null and b/desktop/frontend/src/assets/img/logo-w.png differ diff --git a/desktop/frontend/src/assets/js/files.js b/desktop/frontend/src/assets/js/files.js index 8ce97db..0ac2050 100644 --- a/desktop/frontend/src/assets/js/files.js +++ b/desktop/frontend/src/assets/js/files.js @@ -1,4 +1,4 @@ -import { ReadAll } from '@/../wailsjs/go/main/App.js' +import { ReadAll, SaveFile, PathExists, DirSize, AppPath } from '@/../wailsjs/go/main/App.js' import utils from '@/assets/js/utils' // Temporary cache for file information @@ -186,6 +186,125 @@ function readAll(filePath) { return ReadAll(filePath) } +// Await Read file contents +async function awaitReadAll(filePath) { + return await ReadAll(filePath) +} + +// Save file +function saveFile(filePath, content) { + return SaveFile(filePath, content) +} + +/** + * Reads a TOML node info to a json object + */ +async function readTomlNode(filePath, nodeName) { + const fileContent = await readAll(filePath) + // console.log("Read Toml file, filePath:", filePath, ", fileContent:", fileContent) + if (utils.isBlank(fileContent)) { + utils.pop('readTomlFailed') + return + } + const lines = utils.stringToLines(fileContent) + + // Read Node + const nodeStart = lines.findIndex((line) => { + return line.includes("[" + nodeName + "]") + }) + const node = {} + for (let i = nodeStart + 1; i < lines.length; i++) { + // console.log("line: ", lines[i]) + // Determine whether the next configuration module has been reached. + if (lines[i].startsWith("[")) { + break + } + // 读取配置 + const line = lines[i] + if (line.startsWith("#")) { + continue + } + const lineArr = line.split("=") + if (lineArr.length == 0) { + continue + } + const key = lineArr[0].trim() + let value = "" + if (lineArr.length == 2) { + value = lineArr[1].trim() + } + node[key] = value + } + console.log("Read node from toml file, result: ", node) + return node +} + +/** + * Save a toml node + */ +async function saveTomlNode(filePath, nodeName, newNodeJson) { + const fileContent = await readAll(filePath) + // console.log("Read Toml file, filePath:", filePath, ", fileContent:", fileContent) + if (utils.isBlank(fileContent)) { + utils.pop(t('readTomlFailed')) + return + } + const lines = utils.stringToLines(fileContent) + + // Read Node + const nodeStart = lines.findIndex((line) => { + return line.includes("[" + nodeName + "]") + }) + + for (let i = nodeStart + 1; i < lines.length; i++) { + // console.log("line: ", lines[i]) + // Determine whether the next configuration module has been reached. + if (lines[i].startsWith("[")) { + break + } + // 读取配置 + const line = lines[i] + if (line.startsWith("#")) { + continue + } + const lineArr = line.split("=") + if (lineArr.length == 0) { + continue + } + const key = lineArr[0].trim() + let value = newNodeJson[key] + if (utils.isNull(value)) { + continue + } + value = value.trim() + lines[i] = key + " = " + value + } + console.log("Save node from toml file, new lines: ", lines) + const newContent = lines.join("\n") + await saveFile(filePath, newContent) +} + + +function pathExists(path) { + return PathExists(path) +} + +function dirSize(path) { + return DirSize(path) +} + +async function awaitDirSize(path) { + return await dirSize(path) +} + +function appPath(path) { + return AppPath(path) +} + +async function awaitAppPath(path) { + return await appPath(path) +} + export default { // Cache on onChange cache, @@ -206,5 +325,14 @@ export default { // Collect fileId from Comps fileIds, // Read file - readAll + readAll, + // Read toml node + readTomlNode, + // Save toml node + saveTomlNode, + pathExists, + dirSize, + awaitDirSize, + appPath, + awaitAppPath, } diff --git a/desktop/frontend/src/assets/js/utils.js b/desktop/frontend/src/assets/js/utils.js index 7c6a64c..953aa50 100644 --- a/desktop/frontend/src/assets/js/utils.js +++ b/desktop/frontend/src/assets/js/utils.js @@ -1,4 +1,4 @@ -import { Greet } from '@/../wailsjs/go/main/App.js' +import { Greet, CheckPort } from '@/../wailsjs/go/main/App.js' import axios from "axios" import { ElMessage } from 'element-plus' @@ -82,10 +82,17 @@ async function awaitDel(url, param) { function greet(name) { return Greet(name).then(resp => { console.log("greet resp:", resp) - return resp }) } +function checkPort(port) { + return CheckPort(port) +} + +async function awaitCheckPort(port) { + return await checkPort(port) +} + /** * Check if object is null */ @@ -545,6 +552,7 @@ function stringToLines(str) { } return str.split('\n') } + export default { /** * Synchronous GET HTTP request @@ -687,4 +695,7 @@ export default { stringToLines, + checkPort, + + awaitCheckPort, } diff --git a/desktop/frontend/src/assets/js/verify.js b/desktop/frontend/src/assets/js/verify.js index 4252699..61928bd 100644 --- a/desktop/frontend/src/assets/js/verify.js +++ b/desktop/frontend/src/assets/js/verify.js @@ -90,7 +90,7 @@ const intValidator = (rule, value, callback) => { } function validator() { - console.log("arguments:", arguments) + // console.log("arguments:", arguments) if (arguments.length <= 1) { const type = arguments[0] // Default validation logic, no special characters diff --git a/desktop/frontend/src/assets/md/README.md b/desktop/frontend/src/assets/md/README.md new file mode 100644 index 0000000..d0d04bb --- /dev/null +++ b/desktop/frontend/src/assets/md/README.md @@ -0,0 +1,159 @@ +English | [中文](README_zh.md) + +[![GitHub stars](https://img.shields.io/github/stars/mannaandpoem/OpenManus?style=social)](https://github.com/mannaandpoem/OpenManus/stargazers) +  +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)   +[![Discord Follow](https://dcbadge.vercel.app/api/server/DYn29wFk9z?style=flat)](https://discord.gg/DYn29wFk9z) + +# 👋 OpenManus + +Manus is incredible, but OpenManus can achieve any idea without an *Invite Code* 🛫! + +Our team +members [@mannaandpoem](https://github.com/mannaandpoem) [@XiangJinyu](https://github.com/XiangJinyu) [@MoshiQAQ](https://github.com/MoshiQAQ) [@didiforgithub](https://github.com/didiforgithub) [@stellaHSR](https://github.com/stellaHSR), we are from [@MetaGPT](https://github.com/geekan/MetaGPT). The prototype is launched within 3 hours and we are keeping building! + +It's a simple implementation, so we welcome any suggestions, contributions, and feedback! + +Enjoy your own agent with OpenManus! + +We're also excited to introduce [OpenManus-RL](https://github.com/OpenManus/OpenManus-RL), an open-source project dedicated to reinforcement learning (RL)- based (such as GRPO) tuning methods for LLM agents, developed collaboratively by researchers from UIUC and OpenManus. + +## Project Demo + + + +## Installation + +We provide two installation methods. Method 2 (using uv) is recommended for faster installation and better dependency management. + +### Method 1: Using conda + +1. Create a new conda environment: + +```bash +conda create -n open_manus python=3.12 +conda activate open_manus +``` + +2. Clone the repository: + +```bash +git clone https://github.com/mannaandpoem/OpenManus.git +cd OpenManus +``` + +3. Install dependencies: + +```bash +pip install -r requirements.txt +``` + +### Method 2: Using uv (Recommended) + +1. Install uv (A fast Python package installer and resolver): + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +2. Clone the repository: + +```bash +git clone https://github.com/mannaandpoem/OpenManus.git +cd OpenManus +``` + +3. Create a new virtual environment and activate it: + +```bash +uv venv +source .venv/bin/activate # On Unix/macOS +# Or on Windows: +# .venv\Scripts\activate +``` + +4. Install dependencies: + +```bash +uv pip install -r requirements.txt +``` + +## Configuration + +OpenManus requires configuration for the LLM APIs it uses. Follow these steps to set up your configuration: + +1. Create a `config.toml` file in the `config` directory (you can copy from the example): + +```bash +cp config/config.example.toml config/config.toml +``` + +2. Edit `config/config.toml` to add your API keys and customize settings: + +```toml +# Global LLM configuration +[llm] +model = "gpt-4o" +base_url = "https://api.openai.com/v1" +api_key = "sk-..." # Replace with your actual API key +max_tokens = 4096 +temperature = 0.0 + +# Optional configuration for specific LLM models +[llm.vision] +model = "gpt-4o" +base_url = "https://api.openai.com/v1" +api_key = "sk-..." # Replace with your actual API key +``` + +## Quick Start + +One line for run OpenManus: + +```bash +python main.py +``` + +Then input your idea via terminal! + +For unstable version, you also can run: + +```bash +python run_flow.py +``` + +## How to contribute + +We welcome any friendly suggestions and helpful contributions! Just create issues or submit pull requests. + +Or contact @mannaandpoem via 📧email: mannaandpoem@gmail.com + +## Community Group +Join our networking group on Feishu and share your experience with other developers! + +
+ OpenManus 交流群 +
+ +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=mannaandpoem/OpenManus&type=Date)](https://star-history.com/#mannaandpoem/OpenManus&Date) + +## Acknowledgement + +Thanks to [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo) +and [browser-use](https://github.com/browser-use/browser-use) for providing basic support for this project! + +OpenManus is built by contributors from MetaGPT. Huge thanks to this agent community! + +## Cite +```bibtex +@misc{openmanus2025, + author = {Xinbin Liang and Jinyu Xiang and Zhaoyang Yu and Jiayi Zhang and Sirui Hong}, + title = {OpenManus: An open-source framework for building general AI agents}, + year = {2025}, + publisher = {GitHub}, + journal = {GitHub repository}, + howpublished = {\url{https://github.com/mannaandpoem/OpenManus}}, +} +``` diff --git a/desktop/frontend/src/assets/md/README_zh.md b/desktop/frontend/src/assets/md/README_zh.md new file mode 100644 index 0000000..13bb768 --- /dev/null +++ b/desktop/frontend/src/assets/md/README_zh.md @@ -0,0 +1,148 @@ +[English](README.md) | 中文 + +[![GitHub stars](https://img.shields.io/github/stars/mannaandpoem/OpenManus?style=social)](https://github.com/mannaandpoem/OpenManus/stargazers) +  +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)   +[![Discord Follow](https://dcbadge.vercel.app/api/server/DYn29wFk9z?style=flat)](https://discord.gg/DYn29wFk9z) + +# 👋 OpenManus + +Manus 非常棒,但 OpenManus 无需邀请码即可实现任何创意 🛫! + +我们的团队成员 [@mannaandpoem](https://github.com/mannaandpoem) [@XiangJinyu](https://github.com/XiangJinyu) [@MoshiQAQ](https://github.com/MoshiQAQ) [@didiforgithub](https://github.com/didiforgithub) https://github.com/stellaHSR 来自 [@MetaGPT](https://github.com/geekan/MetaGPT) 组织,我们在 3 +小时内完成了原型开发并持续迭代中! + +这是一个简洁的实现方案,欢迎任何建议、贡献和反馈! + +用 OpenManus 开启你的智能体之旅吧! + +我们也非常高兴地向大家介绍 [OpenManus-RL](https://github.com/OpenManus/OpenManus-RL),这是一个专注于基于强化学习(RL,例如 GRPO)的方法来优化大语言模型(LLM)智能体的开源项目,由来自UIUC 和 OpenManus 的研究人员合作开发。 + +## 项目演示 + + + +## 安装指南 + +我们提供两种安装方式。推荐使用方式二(uv),因为它能提供更快的安装速度和更好的依赖管理。 + +### 方式一:使用 conda + +1. 创建新的 conda 环境: + +```bash +conda create -n open_manus python=3.12 +conda activate open_manus +``` + +2. 克隆仓库: + +```bash +git clone https://github.com/mannaandpoem/OpenManus.git +cd OpenManus +``` + +3. 安装依赖: + +```bash +pip install -r requirements.txt +``` + +### 方式二:使用 uv(推荐) + +1. 安装 uv(一个快速的 Python 包管理器): + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +2. 克隆仓库: + +```bash +git clone https://github.com/mannaandpoem/OpenManus.git +cd OpenManus +``` + +3. 创建并激活虚拟环境: + +```bash +uv venv +source .venv/bin/activate # Unix/macOS 系统 +# Windows 系统使用: +# .venv\Scripts\activate +``` + +4. 安装依赖: + +```bash +uv pip install -r requirements.txt +``` + +## 配置说明 + +OpenManus 需要配置使用的 LLM API,请按以下步骤设置: + +1. 在 `config` 目录创建 `config.toml` 文件(可从示例复制): + +```bash +cp config/config.example.toml config/config.toml +``` + +2. 编辑 `config/config.toml` 添加 API 密钥和自定义设置: + +```toml +# 全局 LLM 配置 +[llm] +model = "gpt-4o" +base_url = "https://api.openai.com/v1" +api_key = "sk-..." # 替换为真实 API 密钥 +max_tokens = 4096 +temperature = 0.0 + +# 可选特定 LLM 模型配置 +[llm.vision] +model = "gpt-4o" +base_url = "https://api.openai.com/v1" +api_key = "sk-..." # 替换为真实 API 密钥 +``` + +## 快速启动 + +一行命令运行 OpenManus: + +```bash +python main.py +``` + +然后通过终端输入你的创意! + +如需体验开发中版本,可运行: + +```bash +python run_flow.py +``` + +## 贡献指南 + +我们欢迎任何友好的建议和有价值的贡献!可以直接创建 issue 或提交 pull request。 + +或通过 📧 邮件联系 @mannaandpoem:mannaandpoem@gmail.com + +## 交流群 + +加入我们的飞书交流群,与其他开发者分享经验! + +
+ OpenManus 交流群 +
+ +## Star 数量 + +[![Star History Chart](https://api.star-history.com/svg?repos=mannaandpoem/OpenManus&type=Date)](https://star-history.com/#mannaandpoem/OpenManus&Date) + +## 致谢 + +特别感谢 [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo) +和 [browser-use](https://github.com/browser-use/browser-use) 为本项目提供的基础支持! + +OpenManus 由 MetaGPT 社区的贡献者共同构建,感谢这个充满活力的智能体开发者社区! diff --git a/desktop/frontend/src/components/AsideMenu.vue b/desktop/frontend/src/components/AsideMenu.vue index 0664be4..ab8dfa9 100644 --- a/desktop/frontend/src/components/AsideMenu.vue +++ b/desktop/frontend/src/components/AsideMenu.vue @@ -1,6 +1,6 @@ diff --git a/desktop/frontend/src/components/MainFrame.vue b/desktop/frontend/src/components/MainFrame.vue index b8730d5..52a5744 100644 --- a/desktop/frontend/src/components/MainFrame.vue +++ b/desktop/frontend/src/components/MainFrame.vue @@ -3,8 +3,9 @@
@@ -60,11 +61,16 @@ import { showShade, closeShade } from '@/assets/js/shade' import { useConfig } from '@/store/config' import { useEventListener } from '@vueuse/core' import { storeToRefs } from 'pinia' +import { useDark } from '@vueuse/core' const router = useRouter() const config = useConfig() +const isDark = useDark() + +const { shrink, collapse, resizeCollapse } = storeToRefs(config) + +const menuCollapse = computed(() => collapse.value || resizeCollapse.value) -const { shrink, menuCollapse } = storeToRefs(config) const currentRoute = reactive(router.currentRoute) // Default transition effect, slide to the left @@ -83,53 +89,50 @@ const menuAnimationDuration = ref(0) // Function to toggle the menu between expanded and collapsed states function menuToggle() { menuAnimationDuration.value = '300ms' - if (menuCollapse.value) { // console.log("Extend menu") if (shrink.value) { // Expend the shade if menu is collapsing showShade(() => { // Callback function to close the shade after the menu has collapsed - config.setMenuCollapse(true) + config.setCollapse(true) }) } + config.setCollapse(false) } else { // If the menu is in an expanded state, close the shade closeShade() + config.setCollapse(true) } - // Toggle the menu state - config.setMenuCollapse(!menuCollapse.value) + resizeCollapse.value = collapse.value + console.log("collapse:", collapse.value, ", menuCollapse:", menuCollapse.value) } +// Adaptive layout function onAdaptiveLayout() { // Get the current window width const clientWidth = document.body.clientWidth - // console.log("menuCollapse:", menuCollapse.value, config.getMenuCollapse(), "clientWidth:", clientWidth) // Determine if the aside menu should be shrunk based on the window width if (clientWidth < 800) { config.setShrink(true) - if (!menuCollapse.value) { - // Collapse the menu if it is not already collapsed - menuToggle() - } + config.setResizeCollapse(true) } else { config.setShrink(false) + config.setResizeCollapse(false) } + closeShade() } +watch(() => router.currentRoute.value, (newValue, oldValue) => { + // Toggle the menu when the route changes + onAdaptiveLayout() +}) + onBeforeMount(() => { onAdaptiveLayout() useEventListener(window, 'resize', onAdaptiveLayout) }) -watch(() => router.currentRoute.value.path, (newValue, oldValue) => { - // If the layout is shrunk and the menu is expanded, collapse the menu - if (shrink.value && !menuCollapse.value) { - menuToggle() - } - -}) - function refresh() { // Reload the page location.reload() @@ -157,7 +160,7 @@ header { } aside { - background-color: var(--el-fg-color); + z-index: 9999999; } aside.shrink { @@ -222,7 +225,6 @@ main { .fixed-menu-expand { position: fixed; - z-index: 9999; height: 44px; width: 200px; /* Reference to the keyframes */ @@ -231,12 +233,13 @@ main { animation-duration: v-bind('menuAnimationDuration'); animation-timing-function: ease-in-out; background-color: var(--el-fg-color); + /* border-bottom: 1px solid var(--el-bg-color); */ z-index: 9999999; } .scrollbar-menu-wrapper { - top: 44px; - height: calc(100vh - 44px); + top: 45px; + height: calc(100vh - 45px); background-color: var(--el-fg-color); } diff --git a/desktop/frontend/src/components/SimpleFrame.vue b/desktop/frontend/src/components/SimpleFrame.vue new file mode 100644 index 0000000..6ededad --- /dev/null +++ b/desktop/frontend/src/components/SimpleFrame.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/desktop/frontend/src/components/TopHeader.vue b/desktop/frontend/src/components/TopHeader.vue index 83d2ab5..4c84f04 100644 --- a/desktop/frontend/src/components/TopHeader.vue +++ b/desktop/frontend/src/components/TopHeader.vue @@ -55,23 +55,27 @@ + + diff --git a/desktop/frontend/src/views/config/General.vue b/desktop/frontend/src/views/config/General.vue index 5ecf8a3..721f532 100644 --- a/desktop/frontend/src/views/config/General.vue +++ b/desktop/frontend/src/views/config/General.vue @@ -115,7 +115,7 @@ const baseEdit = computed(() => { }) const baseNoData = computed(() => { - return baseShow && serverConfig.model == null + return baseShow }) const serverShow = computed(() => { @@ -126,7 +126,7 @@ const serverEdit = computed(() => { return viewModel.server == 'edit' }) -const readConfigSuccess = ref(false) +const readConfigSuccess = ref(true) const serverNoData = computed(() => { return serverShow && !readConfigSuccess.value @@ -147,43 +147,51 @@ function clearCache() { utils.pop(t('clearCacheSuccess')) } -onMounted(() => { - // 读取配置文件config/config.toml - files.readAll("@/../../config/config.toml").then((fileContent) => { - console.log("config/config.toml: ", fileContent) - if (utils.notBlank(fileContent)) { - readConfigSuccess.value = true - } else { - utils.pop(t('readConfigFailed')) - return - } - const lines = utils.stringToLines(fileContent) +const appDataPath = ref() - // 读取[server] - const serverStart = lines.findIndex((line) => { - return line.includes("[server]") - }) - for (let i = serverStart + 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() - serverConfig[key] = value +onMounted(async () => { + await files.awaitAppPath().then((path) => { + appDataPath.value = path + console.log('appDataPath: ', appDataPath.value) + if (appDataPath.value && appDataPath.value.endsWith('\\desktop\\build\\bin')) { + appDataPath.value = appDataPath.value.replace('\\desktop\\build\\bin', '') } - console.log("serverConfig read from file: ", serverConfig) - utils.copyProps(serverConfig, serverConfigUpd) + console.log('appDataPath: ', appDataPath.value) }) + // 读取配置文件config/config.toml + loadServerConfig() }) +function loadServerConfig() { + const filePath = appDataPath.value + "\\config\\config.toml" + files.readTomlNode(filePath, "server").then((node) => { + console.log("config/config.toml: ", node) + if (utils.isBlank(node)) { + readConfigSuccess.value = false + utils.pop(t('readTomlFailed')) + return + } + utils.copyProps(node, serverConfig) + utils.copyProps(node, serverConfigUpd) + }) +} + +function saveServerConfig() { + const filePath = appDataPath.value + "\\config\\config.toml" + files.readAll(filePath) + files.saveTomlNode(filePath, "server", serverConfigUpd).then((resp) => { + console.log("config/config.toml: ", resp) + loadServerConfig() + toShow('server') + }) +} + +const ruleFormRef = ref() + +const rules = reactive({ + host: [{ validator: verify.validator('notBlank'), trigger: 'blur' }], + port: [{ validator: verify.validator('notBlank'), trigger: 'blur' }], +}) const submitForm = async () => { try { @@ -194,20 +202,13 @@ const submitForm = async () => { return } ElMessage.success('验证通过,提交表单'); - // update() + saveServerConfig() } catch (error) { + console.log('error: ', error); ElMessage.error('参数验证失败'); } } -const rules = reactive({ - host: [{ validator: verify.validator('notBlank'), trigger: 'blur' }], - port: [{ 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' }], -}) - diff --git a/desktop/frontend/src/views/config/Init.vue b/desktop/frontend/src/views/config/Init.vue new file mode 100644 index 0000000..d0adc31 --- /dev/null +++ b/desktop/frontend/src/views/config/Init.vue @@ -0,0 +1,525 @@ + + + + + diff --git a/desktop/frontend/src/views/config/Llm.vue b/desktop/frontend/src/views/config/Llm.vue index d6e613b..9d24696 100644 --- a/desktop/frontend/src/views/config/Llm.vue +++ b/desktop/frontend/src/views/config/Llm.vue @@ -131,7 +131,7 @@ const baseEdit = computed(() => { return viewModel.base == 'edit' }) -const readConfigSuccess = ref(false) +const readConfigSuccess = ref(true) const baseNoData = computed(() => { return baseShow && !readConfigSuccess.value @@ -158,43 +158,54 @@ function clearCache() { utils.pop(t('clearCacheSuccess')) } -onMounted(() => { - // 读取配置文件config/config.toml - files.readAll("@/../../config/config.toml").then((fileContent) => { - console.log("config/config.toml: ", fileContent) - if (utils.notBlank(fileContent)) { - readConfigSuccess.value = true - } else { - utils.pop(t('readConfigFailed')) - return - } - const lines = utils.stringToLines(fileContent) +const appDataPath = ref() - // 读取[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 +onMounted(async () => { + await files.awaitAppPath().then((path) => { + appDataPath.value = path + console.log('appDataPath: ', appDataPath.value) + if (appDataPath.value && appDataPath.value.endsWith('\\desktop\\build\\bin')) { + appDataPath.value = appDataPath.value.replace('\\desktop\\build\\bin', '') } - console.log("llmConfig read from file: ", llmConfig) - utils.copyProps(llmConfig, llmConfigUpd) + console.log('appDataPath: ', appDataPath.value) }) + // 读取配置文件config/config.toml + loadLlmConfig() }) +function loadLlmConfig() { + const filePath = appDataPath.value + "\\config\\config.toml" + files.readTomlNode(filePath, "llm").then((node) => { + console.log("config/config.toml: ", node) + if (utils.isBlank(node)) { + readConfigSuccess.value = false + utils.pop(t('readTomlFailed')) + return + } + utils.copyProps(node, llmConfig) + utils.copyProps(node, llmConfigUpd) + }) +} + +function saveLlmConfig() { + const filePath = appDataPath.value + "\\config\\config.toml" + files.readAll(filePath) + files.saveTomlNode(filePath, "llm", llmConfigUpd).then((resp) => { + console.log("config/config.toml: ", resp) + loadLlmConfig() + toShow('llm') + }) +} + +const ruleFormRef = ref() + +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' }], +}) const submitForm = async () => { try { @@ -205,20 +216,13 @@ const submitForm = async () => { return } ElMessage.success('验证通过,提交表单'); - // update() + saveLlmConfig() + toShow('base') } 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' }], -}) - diff --git a/desktop/frontend/src/views/config/Theme.vue b/desktop/frontend/src/views/config/Theme.vue deleted file mode 100644 index bf659d4..0000000 --- a/desktop/frontend/src/views/config/Theme.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - - - diff --git a/desktop/frontend/src/views/task/Home.vue b/desktop/frontend/src/views/task/Home.vue deleted file mode 100644 index 9d93795..0000000 --- a/desktop/frontend/src/views/task/Home.vue +++ /dev/null @@ -1,407 +0,0 @@ - - - - - diff --git a/desktop/frontend/src/views/task/TaskIndex.vue b/desktop/frontend/src/views/task/TaskIndex.vue index 4bf8cd3..2282268 100644 --- a/desktop/frontend/src/views/task/TaskIndex.vue +++ b/desktop/frontend/src/views/task/TaskIndex.vue @@ -1,7 +1,7 @@ diff --git a/desktop/frontend/src/views/task/TaskInfo.vue b/desktop/frontend/src/views/task/TaskInfo.vue index f3c29a2..e098467 100644 --- a/desktop/frontend/src/views/task/TaskInfo.vue +++ b/desktop/frontend/src/views/task/TaskInfo.vue @@ -1,5 +1,5 @@