问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

OpenManus 实时控制台

创作时间:
作者:
@小白创作中心

OpenManus 实时控制台

引用
CSDN
1.
https://m.blog.csdn.net/xiatian_win123/article/details/146159734

OpenManus是一个开源项目,本文将详细介绍在Windows系统上部署OpenManus的完整步骤。从环境搭建到前端页面的搭建,提供详细的步骤和代码示例,帮助读者快速上手。

一、环境搭建

本教程基于Windows 10系统,其他版本的Windows系统可能略有不同,但基本步骤相似。OpenManus的Git地址:https://github.com/mannaandpoem/OpenManus

根据官网的两种安装推荐方式如下:

这里推荐使用第一种方式,虽然第二种方式使用UV,但第一种方式更为常见。

二、Anaconda下载

如果电脑上没有安装Anaconda,需要先进行安装。这里推荐两种下载方式:官网下载和镜像下载。官网下载地址:https://www.anaconda.com/products/distribution

镜像下载地址:https://mirrors.bfsu.edu.cn/anaconda/archive/

建议选择Anaconda3-5.3.1-Windows-x86_64.exe这个镜像版本。具体Anaconda的安装步骤可以自行搜索。

三、核心组件安装

以下是为Windows 10系统适配的详细步骤(使用Anaconda):

  1. 安装前置工具
  1. 打开Anaconda Prompt(推荐)或PowerShell

  2. 创建conda环境

conda create -n open_manus python=3.12 -y
conda activate open_manus
  1. 克隆仓库
git clone https://github.com/mannaandpoem/OpenManus.git
cd OpenManus
  1. 安装依赖
pip install -r requirements.txt

常见问题解决方案:

  1. 如果遇到环境激活问题:
  • 在PowerShell中首次使用需执行:
conda init powershell
  • 关闭后重新打开终端
  1. 如果缺少C++编译工具(安装某些包时报错):
  • 安装Microsoft C++ Build Tools
  1. 如果Python 3.12不可用:
conda create -n open_manus python=3.11
  1. 替代克隆方式(如果git不可用):

至此,本地环境已经安装完毕。

四、服务配置与连接

找到Config文件夹,进去,重新复制一份config.toml文件。这里需要替换config文件配置:

# Global LLM configuration
[llm]
model = "Qwen/QwQ-32B" 
base_url = "https://api.siliconflow.cn/v1"
api_key = "这里写你自己的Key"
max_tokens = 16384
temperature = 0.6

# Optional configuration for specific LLM models
[llm.vision]
model = "Qwen/Qwen2-VL-72B-Instruct" 
base_url = "https://api.siliconflow.cn/v1"
api_key = "这里写你自己的Key"

这里使用硅基流动的Key,他们家刚好有活动,注册就送2000万Tokens,完全够测试使用。注册地址是:https://cloud.siliconflow.cn/i/pCa1dBVX

配置完毕之后,运行main.py文件。

五、前端页面

为了方便使用,可以使用Flask搭建一个简单的前端页面。以下是两个主要文件:app.py和templates/index.html。

app.py

from flask import Flask, Response, request, jsonify, render_template
from flask_cors import CORS
import subprocess
import sys
import os
import signal
import threading

app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "*"}})

# 进程管理
process_lock = threading.Lock()
current_process = None

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/stream', methods=['GET', 'POST', 'OPTIONS'])
def stream_execute():
    global current_process
    
    # 处理预检请求
    if request.method == 'OPTIONS':
        return _build_preflight_response()
    
    # 获取输入内容
    idea = request.json.get('idea', '') if request.method == 'POST' else request.args.get('idea', '')
    if not idea:
        return Response("data: 错误:未提供输入\n\n", mimetype='text/event-stream')
    
    # 终止已有进程
    with process_lock:
        if current_process and current_process.poll() is None:
            current_process.terminate()
        
        # 启动新进程
        current_process = subprocess.Popen(
            [sys.executable, '-u', 'main.py'],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1,
            universal_newlines=True,
            creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0
        )
        
        # 发送输入
        current_process.stdin.write(idea + '\n')
        current_process.stdin.flush()
    
    # 流式响应
    def generate():
        while True:
            line = current_process.stdout.readline()
            if not line:
                if current_process.poll() is not None:
                    break
                continue
            yield f"data: {line}\n\n"
        yield "event: end\ndata: \n\n"
    
    return Response(
        generate(),
        mimetype='text/event-stream',
        headers={
            'Access-Control-Allow-Origin': '*',
            'Cache-Control': 'no-cache',
            'X-Accel-Buffering': 'no'
        }
    )

def _build_preflight_response():
    response = jsonify({'status': 'ok'})
    response.headers.add("Access-Control-Allow-Origin", "*")
    response.headers.add("Access-Control-Allow-Headers", "*")
    response.headers.add("Access-Control-Allow-Methods", "*")
    return response

@app.route('/stop', methods=['POST'])
def stop_execution():
    global current_process
    with process_lock:
        if current_process and current_process.poll() is None:
            current_process.terminate()
            return jsonify({'status': 'stopped'})
    return jsonify({'status': 'not running'}), 404

@app.after_request
def add_cors_headers(response):
    response.headers['Access-Control-Allow-Origin'] = '*'
    response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
    return response

if __name__ == '__main__':
    app.run(port=5000, threaded=True)

index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>OpenManus 实时控制台</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
    <style>
        #output {
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 20px;
            border-radius: 5px;
            font-family: 'Consolas', monospace;
            height: 60vh;
            overflow-y: auto;
            white-space: pre-wrap;
        }
        .log-item {
            margin: 5px 0;
            padding: 3px 10px;
            border-left: 3px solid #3c3c3c;
        }
        .loading {
            position: fixed;
            top: 20px;
            right: 20px;
            display: none;
        }
    </style>
</head>
<body>
    <div class="container py-5">
        <h1 class="text-primary mb-4">🚀 OpenManus 实时控制台</h1>
        
        <div class="mb-4">
            <textarea id="ideaInput" class="form-control bg-dark text-light" 
                      rows="4" placeholder="输入您的创意(示例:分析特斯拉最近三个月的股价趋势并生成可视化报告)"></textarea>
        </div>
        
        <div class="d-flex gap-2 mb-4">
            <button class="btn btn-success" onclick="startExecution()">开始执行</button>
            <button class="btn btn-danger" onclick="stopExecution()">终止任务</button>
        </div>
        
        <div class="card bg-dark">
            <div class="card-header text-light">实时输出</div>
            <div id="output" class="card-body"></div>
        </div>
        
        <div id="loading" class="loading">
            <div class="spinner-border text-primary" role="status">
                <span class="visually-hidden">加载中...</span>
            </div>
        </div>
    </div>
    <script>
        let eventSource = null;
        function showLoading() {
            document.getElementById('loading').style.display = 'block';
        }
        function hideLoading() {
            document.getElementById('loading').style.display = 'none';
        }
        function clearOutput() {
            document.getElementById('output').innerHTML = '';
        }
        function showError(message) {
            const output = document.getElementById('output');
            output.innerHTML += `<div class="text-danger">${message}</div>`;
        }
        function ansiToHtml(text) {
            return text
                .replace(/\u001B\[32m/g, '<span class="text-success">')
                .replace(/\u001B\[31m/g, '<span class="text-danger">')
                .replace(/\u001B\[0m/g, '</span>');
        }
        function startExecution() {
            const idea = document.getElementById('ideaInput').value.trim();
            if (!idea) return alert('请输入执行内容');
            
            clearOutput();
            showLoading();
            // 先发送POST请求
            fetch('http://localhost:5000/stream', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ idea: idea })
            })
            .then(response => {
                if (!response.ok) {
                    throw new Error(`HTTP错误 ${response.status}`);
                }
                // 建立SSE连接
                eventSource = new EventSource(`http://localhost:5000/stream?idea=${encodeURIComponent(idea)}`);
                
                eventSource.onmessage = (e) => {
                    const formatted = ansiToHtml(e.data);
                    document.getElementById('output').innerHTML += 
                        `<div class="log-item">${formatted}</div>`;
                    // 自动滚动
                    const output = document.getElementById('output');
                    output.scrollTop = output.scrollHeight;
                };
                eventSource.onerror = (e) => {
                    console.error('SSE Error:', e);
                    hideLoading();
                    eventSource.close();
                };
                eventSource.addEventListener('end', () => {
                    hideLoading();
                    eventSource.close();
                });
            })
            .catch(error => {
                hideLoading();
                showError(`请求失败: ${error.message}`);
            });
        }
        function stopExecution() {
            if (eventSource) {
                eventSource.close();
                hideLoading();
            }
            fetch('http://localhost:5000/stop', {
                method: 'POST'
            })
            .then(response => {
                if (response.ok) {
                    alert('已终止执行');
                }
            });
        }
        // 清理资源
        window.addEventListener('beforeunload', () => {
            if (eventSource) eventSource.close();
            fetch('/stop', { method: 'POST' });
        });
    </script>
</body>
</html>

启动服务:

在激活open_manus3.12的Python环境下,运行以下命令启动服务:

python app.py

启动服务完毕后,可以在浏览器中访问:http://localhost:5000/

至此,OpenManus的部署和前端页面搭建已经完成。如果在使用过程中遇到任何问题,欢迎在社区中互相交流。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号