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)
+
+[](https://github.com/mannaandpoem/OpenManus/stargazers)
+
+[](https://opensource.org/licenses/MIT)
+[](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!
+
+
+

+
+
+## Star History
+
+[](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) | 中文
+
+[](https://github.com/mannaandpoem/OpenManus/stargazers)
+
+[](https://opensource.org/licenses/MIT)
+[](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
+
+## 交流群
+
+加入我们的飞书交流群,与其他开发者分享经验!
+
+
+

+
+
+## Star 数量
+
+[](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 @@
+
+
+
+
+
+
{{ t('initConfig') }}
+
+
+
+
+
+
+
+
+
+
+ {{ t('envLibDownload') }}:
+
+ {{ envLibDownloadLoading ? t('envLibDownloading')
+ : (initCheck.envLib ? t('checkedPass') : t('checkedFailed')) }}
+
+
+
+ {{ t('envLibDownload') }}
+
+
+
+
+
+
+ {{ t('envLibDownloadTips') }}
+
+ {{ envLibDownloadProgress.text }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('serverConfig') }}:
+ {{ initCheck.serverConfig ? t('checkedPass') : t('checkedFailed') }}
+
+
{{ t('edit') }}
+
+
+
+
+
+
+ host:
+ {{ serverConfig.host }}
+
+
+
+ port:
+ {{ serverConfig.port }}
+
+
+
+
+
+
+
+ {{ t('step2') }}
+
+
+
+
+
+
+ port:
+
+
+
+
+
+
+ {{ t('cancel') }}
+ {{ t('submit') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('llmConfig') }}:
+ {{ initCheck.llmConfig ? t('checkedPass') : t('checkedFailed') }}
+
+
+
+ {{ t('edit') }}
+
+
+ {{ t('cancel') }}
+
+
+
+
+
+
+
+
+ model:
+ {{ llmConfig.model }}
+
+
+
+ base_url:
+ {{ llmConfig.base_url }}
+
+
+
+ api_key:
+ {{ llmConfig.api_key }}
+
+
+
+ max_tokens:
+ {{ llmConfig.max_tokens }}
+
+
+
+ temperature:
+ {{ llmConfig.temperature }}
+
+
+
+
+
+
+
+ model:
+
+
+
+
+
+
+ base_url:
+
+
+
+
+
+
+ api_key:
+
+
+
+
+
+
+ max_tokens:
+
+
+
+
+
+
+ temperature:
+
+
+
+
+
+
+ {{ t('cancel') }}
+ {{ t('submit') }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('startTheExperience') }}
+
+
+
+
+
+
+
+
+
+
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 @@
-
-
-
-
-
-
-
-
-
- {{ t('user') }}
-
-
- {{ taskInfo.prompt }}
-
-
-
-
-
-
OpenManus-AI
-
-
-
-
-
-
-
-
- STEP
-
- {{ step.result }}
-
-
-
-
- {{ subStep.type }}
- {{ subStep.createdDt }}
-
-
- {{ subStep.result }}
-
-
-
-
-
-
-
-
-
-
- 任务状态:
- {{ taskInfo.status }}
-
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
+
+
+
-
OpenManus-AI
-
+
+
OpenManus-AI
+
+
+ {{ t('expandAll') }}
+
+
+ {{ t('collapseAll') }}
+
+
+
-
-
-
-
-
- {{ t('step') }}
-
- {{ step.result }}
-
-
-
-
- {{ subStep.type }}
- {{ subStep.createdDt }}
-
+
+
+
+
- {{ subStep.result }}
+
+ {{ t('step') }}
+
+ {{ step.result }}
-
+
{{ step.createdDt }}
-
-
-
+
+
+
+ {{ subStep.type }}
+ {{ subStep.createdDt }}
+
+
+ {{ subStep.result }}
+
+
+
+
+
+
-