构建下一代AI开发环境:使用Python与HTML打造通用脚手架与简易AI IDE

引言:重新定义AI开发体验

在人工智能技术飞速发展的今天,开发环境的高效性直接决定了算法工程师和研究人员的工作效率。传统IDE虽然功能强大,但往往过于臃肿,且对AI工作流的支持有限。本文将深入探讨如何利用Python和HTML构建一个轻量级、高度可定制的通用脚手架和AI专用集成开发环境。

现代AI开发面临着多重挑战:环境配置复杂、依赖管理困难、计算资源调度不透明、实验跟踪繁琐等。通过构建专用的AI IDE,我们能够为机器学习项目提供从原型设计到部署的全流程支持,显著提升开发效率。

这个项目不仅是一个技术实践,更是对AI开发工作流的一次重新思考。我们将从基础架构开始,逐步构建一个功能完整的开发环境,涵盖代码编辑、项目管理、模型训练可视化等核心功能。

一、项目架构设计与技术选型

1.1 整体架构概述

构建AI IDE需要兼顾前后端分离的现代Web开发理念和AI计算任务的特殊性。我们采用以下架构设计:

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│  前端界面层     │    │   API网关层      │    │  后端服务层     │
│  (HTML/CSS/JS)  │◄──►│  (Flask路由)     │◄──►│ (Python核心逻辑)│
└─────────────────┘    └──────────────────┘    └─────────────────┘
         │                        │                        │
         │                        │                        │
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│  代码编辑器     │    │  文件管理服务    │    │  AI模型服务     │
│  (Monaco)       │    │  (OS操作)        │    │ (TensorFlow/PyTorch)
└─────────────────┘    └──────────────────┘    └─────────────────┘

这种架构允许前端专注于用户交互体验,后端处理复杂的计算任务,两者通过清晰的API接口进行通信。这种分离使得我们可以独立升级各个组件,提高了系统的可维护性和扩展性。

1.2 技术栈选择理由

前端技术选型

  • HTML5/CSS3:构建语义化、响应式界面基础
  • JavaScript (ES6+):实现复杂的交互逻辑
  • Monaco Editor:VS Code使用的代码编辑器,提供丰富的编辑功能
  • Bootstrap 5:快速构建美观的UI组件

后端技术选型

  • Python 3.8+:AI生态系统的首选语言
  • Flask框架:轻量级、灵活的Web框架
  • WebSocket:实现实时通信,用于日志输出和进度更新
  • SQLite:轻量级数据库,用于项目配置存储

选择这些技术主要基于它们的成熟度、社区支持度以及对AI工作流的适配性。Python在AI领域的统治地位毋庸置疑,而Flask的轻量级特性使得我们可以快速迭代开发。

二、前端界面设计与实现

2.1 基础HTML结构搭建

前端界面是用户与AI IDE交互的主要入口,需要精心设计以提供流畅的开发体验。我们从基础布局开始构建:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI开发环境 - 智能IDE</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
    <style>
        :root {
            --primary-color: #2c3e50;
            --secondary-color: #34495e;
            --accent-color: #3498db;
            --text-color: #ecf0f1;
            --sidebar-width: 250px;
        }
        
        body {
            margin: 0;
            padding: 0;
            background-color: var(--primary-color);
            color: var(--text-color);
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            overflow: hidden;
        }
        
        #app-container {
            display: flex;
            height: 100vh;
        }
        
        .sidebar {
            width: var(--sidebar-width);
            background-color: var(--secondary-color);
            border-right: 1px solid #1a252f;
            display: flex;
            flex-direction: column;
        }
        
        .main-content {
            flex: 1;
            display: flex;
            flex-direction: column;
        }
        
        .editor-container {
            flex: 1;
            position: relative;
        }
        
        .terminal {
            height: 200px;
            background-color: #1a1a1a;
            border-top: 1px solid #333;
            font-family: 'Courier New', monospace;
            overflow-y: auto;
        }
    </style>
</head>
<body>
    <div id="app-container">
        <!-- 侧边栏 -->
        <div class="sidebar">
            <div class="sidebar-header p-3">
                <h4 class="mb-0">AI工作区</h4>
            </div>
            <div class="sidebar-content flex-grow-1">
                <!-- 文件树和工具将在这里渲染 -->
            </div>
        </div>
        
        <!-- 主内容区 -->
        <div class="main-content">
            <!-- 顶部工具栏 -->
            <nav class="navbar navbar-dark bg-dark py-2">
                <div class="container-fluid">
                    <div class="btn-group">
                        <button class="btn btn-outline-light btn-sm" id="run-btn">
                            <i class="fas fa-play"></i> 运行
                        </button>
                        <button class="btn btn-outline-light btn-sm" id="save-btn">
                            <i class="fas fa-save"></i> 保存
                        </button>
                    </div>
                    <div class="d-flex">
                        <span class="navbar-text" id="status-indicator">
                            <i class="fas fa-circle text-success"></i> 就绪
                        </span>
                    </div>
                </div>
            </nav>
            
            <!-- 代码编辑器区域 -->
            <div class="editor-container">
                <div id="editor" style="height: 100%;"></div>
            </div>
            
            <!-- 终端输出区域 -->
            <div class="terminal p-2">
                <div id="terminal-output"></div>
            </div>
        </div>
    </div>

    <!-- 引入必要的JavaScript库 -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://unpkg.com/monaco-editor@0.33.0/min/vs/loader.js"></script>
    <script src="app.js"></script>
</body>
</html>

这段HTML代码构建了IDE的基本骨架,采用了经典的侧边栏+主编辑区+终端的三分区布局。通过CSS变量定义了一套深色主题的颜色方案,这种配色方案能够减少长时间编码带来的视觉疲劳。布局使用Flexbox实现,确保了在不同屏幕尺寸下的适应性。

侧边栏用于展示文件树和项目管理工具,主编辑区承载代码编辑器,底部终端区域用于显示代码执行输出和系统日志。顶部工具栏提供常用的操作按钮,如运行、保存等,状态指示器实时反馈系统状态。

2.2 Monaco编辑器集成与配置

Monaco Editor是VS Code的编辑核心,提供了丰富的代码编辑功能。我们需要在前端JavaScript中对其进行初始化和配置:

// 初始化Monaco编辑器
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.33.0/min/vs' }});

let editor;

require(['vs/editor/editor.main'], function() {
    // 创建编辑器实例
    editor = monaco.editor.create(document.getElementById('editor'), {
        value: `# 欢迎使用AI开发环境\n# 请输入您的Python代码\n\nprint("Hello, AI World!")\n\n# 示例:简单的机器学习代码\nfrom sklearn.datasets import load_iris\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.ensemble import RandomForestClassifier\n\n# 加载数据\niris = load_iris()\nX_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.2)\n\n# 训练模型\nclf = RandomForestClassifier()\nclf.fit(X_train, y_train)\n\n# 评估模型\naccuracy = clf.score(X_test, y_test)\nprint(f"模型准确率: {accuracy:.2f}")`,
        language: 'python',
        theme: 'vs-dark',
        fontSize: 14,
        lineNumbers: 'on',
        roundedSelection: false,
        scrollBeyondLastLine: false,
        readOnly: false,
        cursorStyle: 'line',
        automaticLayout: true,
        minimap: {
            enabled: true
        },
        suggestOnTriggerCharacters: true,
        quickSuggestions: true
    });
    
    // 监听内容变化
    editor.onDidChangeModelContent(function(event) {
        // 标记文件为已修改
        document.title = document.title.replace(/^\*?/, '*');
        updateSaveStatus(false);
    });
    
    // 注册快捷键
    editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function() {
        saveFile();
    });
    
    console.log('Monaco编辑器初始化完成');
});

// 更新保存状态
function updateSaveStatus(isSaved) {
    const saveBtn = document.getElementById('save-btn');
    const title = document.title;
    
    if (isSaved) {
        document.title = title.replace(/^\*/, '');
        saveBtn.classList.remove('btn-warning');
        saveBtn.classList.add('btn-outline-light');
    } else {
        if (!title.startsWith('*')) {
            document.title = '*' + title;
        }
        saveBtn.classList.remove('btn-outline-light');
        saveBtn.classList.add('btn-warning');
    }
}

// 保存文件函数
function saveFile() {
    const content = editor.getValue();
    
    // 发送保存请求到后端
    fetch('/api/save', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            filename: 'current_script.py',
            content: content
        })
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            updateSaveStatus(true);
            addTerminalOutput('文件保存成功: ' + data.filename);
        } else {
            addTerminalOutput('保存失败: ' + data.error);
        }
    })
    .catch(error => {
        console.error('保存错误:', error);
        addTerminalOutput('保存错误: ' + error.message);
    });
}

这段JavaScript代码完成了Monaco编辑器的初始化和基本功能配置。首先通过require.js加载Monaco Editor的核心库,然后创建编辑器实例并配置各种选项,包括语言设置为Python、使用深色主题、启用行号和缩略图等。

编辑器内容变化监听器检测用户修改,通过修改文档标题和按钮样式提示用户保存。快捷键注册使得用户可以使用Ctrl+S(或Cmd+S)快速保存文件。保存功能通过Fetch API与后端通信,将当前编辑内容发送到服务器保存,并在终端区域显示操作结果。

2.3 文件树组件实现

文件树是IDE的重要组成部分,允许用户浏览和管理项目文件:

// 文件树组件
class FileTree {
    constructor(container) {
        this.container = container;
        this.files = [];
        this.selectedFile = null;
        
        this.init();
        this.loadFileTree();
    }
    
    init() {
        // 创建文件树HTML结构
        this.container.innerHTML = `
            <div class="file-tree-header p-2 border-bottom">
                <div class="input-group input-group-sm">
                    <input type="text" class="form-control" placeholder="搜索文件..." id="file-search">
                    <button class="btn btn-outline-secondary" type="button" id="refresh-files">
                        <i class="fas fa-sync-alt"></i>
                    </button>
                </div>
            </div>
            <div class="file-tree-body flex-grow-1 overflow-auto">
                <ul class="list-group list-group-flush" id="file-list"></ul>
            </div>
            <div class="file-tree-footer p-2 border-top">
                <button class="btn btn-primary btn-sm w-100" id="new-file-btn">
                    <i class="fas fa-plus"></i> 新建文件
                </button>
            </div>
        `;
        
        // 绑定事件
        document.getElementById('refresh-files').addEventListener('click', () => this.loadFileTree());
        document.getElementById('new-file-btn').addEventListener('click', () => this.createNewFile());
        document.getElementById('file-search').addEventListener('input', (e) => this.filterFiles(e.target.value));
    }
    
    async loadFileTree() {
        try {
            const response = await fetch('/api/files');
            const files = await response.json();
            this.files = files;
            this.renderFileTree();
        } catch (error) {
            console.error('加载文件树失败:', error);
            addTerminalOutput('加载文件树失败: ' + error.message);
        }
    }
    
    renderFileTree(filter = '') {
        const fileList = document.getElementById('file-list');
        fileList.innerHTML = '';
        
        const filteredFiles = this.files.filter(file => 
            file.name.toLowerCase().includes(filter.toLowerCase())
        );
        
        filteredFiles.forEach(file => {
            const li = document.createElement('li');
            li.className = 'list-group-item file-item d-flex justify-content-between align-items-center';
            li.dataset.filename = file.name;
            
            const icon = file.type === 'directory' ? 
                '<i class="fas fa-folder text-warning"></i>' : 
                '<i class="fas fa-file-code text-info"></i>';
            
            li.innerHTML = `
                <div class="file-info">
                    ${icon}
                    <span class="file-name ms-2">${file.name}</span>
                </div>
                ${file.type === 'file' ? '<span class="badge bg-secondary">${file.extension}</span>' : ''}
            `;
            
            li.addEventListener('click', () => this.selectFile(file));
            li.addEventListener('dblclick', () => {
                if (file.type === 'file') {
                    this.openFile(file);
                }
            });
            
            fileList.appendChild(li);
        });
    }
    
    filterFiles(query) {
        this.renderFileTree(query);
    }
    
    selectFile(file) {
        // 移除之前选中的文件高亮
        document.querySelectorAll('.file-item').forEach(item => {
            item.classList.remove('active');
        });
        
        // 高亮当前选中的文件
        const selectedItem = document.querySelector(`[data-filename="${file.name}"]`);
        if (selectedItem) {
            selectedItem.classList.add('active');
        }
        
        this.selectedFile = file;
    }
    
    async openFile(file) {
        try {
            const response = await fetch(`/api/file/${encodeURIComponent(file.name)}`);
            const fileData = await response.json();
            
            // 更新编辑器内容
            editor.setValue(fileData.content);
            
            // 根据文件扩展名设置语言模式
            const language = this.getLanguageFromExtension(file.extension);
            monaco.editor.setModelLanguage(editor.getModel(), language);
            
            addTerminalOutput(`已打开文件: ${file.name}`);
            updateSaveStatus(true);
        } catch (error) {
            console.error('打开文件失败:', error);
            addTerminalOutput('打开文件失败: ' + error.message);
        }
    }
    
    getLanguageFromExtension(extension) {
        const languageMap = {
            '.py': 'python',
            '.js': 'javascript',
            '.html': 'html',
            '.css': 'css',
            '.json': 'json',
            '.md': 'markdown'
        };
        
        return languageMap[extension] || 'plaintext';
    }
    
    async createNewFile() {
        const filename = prompt('请输入新文件名:');
        if (!filename) return;
        
        try {
            const response = await fetch('/api/file', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    filename: filename,
                    content: '# 新文件\nprint("Hello, World!")'
                })
            });
            
            const result = await response.json();
            
            if (result.success) {
                addTerminalOutput(`文件创建成功: ${filename}`);
                this.loadFileTree(); // 刷新文件树
            } else {
                addTerminalOutput('文件创建失败: ' + result.error);
            }
        } catch (error) {
            console.error('创建文件失败:', error);
            addTerminalOutput('创建文件失败: ' + error.message);
        }
    }
}

// 初始化文件树
document.addEventListener('DOMContentLoaded', function() {
    const sidebarContent = document.querySelector('.sidebar-content');
    window.fileTree = new FileTree(sidebarContent);
});

文件树组件类封装了文件浏览和管理的所有功能。构造函数初始化HTML结构并绑定事件处理函数。文件树包含搜索框、刷新按钮和新建文件按钮,提供了完整的文件操作界面。

loadFileTree方法从后端API获取文件列表,renderFileTree方法将文件数据渲染为可视化的树形结构。文件根据类型显示不同的图标,Python文件使用代码文件图标,文件夹使用文件夹图标。单击文件选中,双击文件在编辑器中打开。

打开文件时,根据文件扩展名自动设置编辑器的语言模式,为不同编程语言提供语法高亮和代码补全。新建文件功能通过提示框获取文件名,然后向后端发送创建请求。

2.4 终端输出组件

终端组件用于显示代码执行结果和系统消息:

// 终端输出管理
class TerminalOutput {
    constructor(container) {
        this.container = container;
        this.maxLines = 1000; // 最大保留行数
        this.lineCount = 0;
    }
    
    addOutput(message, type = 'info') {
        const line = document.createElement('div');
        line.className = `terminal-line ${type}`;
        
        const timestamp = new Date().toLocaleTimeString();
        const formattedMessage = this.formatMessage(message, type);
        
        line.innerHTML = `<span class="text-muted">[${timestamp}]</span> ${formattedMessage}`;
        
        this.container.appendChild(line);
        this.lineCount++;
        
        // 限制行数,防止内存泄漏
        if (this.lineCount > this.maxLines) {
            this.container.removeChild(this.container.firstChild);
            this.lineCount--;
        }
        
        // 自动滚动到底部
        this.container.scrollTop = this.container.scrollHeight;
    }
    
    formatMessage(message, type) {
        const typeIcons = {
            'info': '<i class="fas fa-info-circle text-primary"></i>',
            'error': '<i class="fas fa-exclamation-circle text-danger"></i>',
            'warning': '<i class="fas fa-exclamation-triangle text-warning"></i>',
            'success': '<i class="fas fa-check-circle text-success"></i>',
            'output': '<i class="fas fa-angle-right text-muted"></i>'
        };
        
        const icon = typeIcons[type] || typeIcons.info;
        
        // 对消息进行HTML转义,防止XSS攻击
        const escapedMessage = message
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#039;');
        
        return `${icon} <span class="terminal-message">${escapedMessage}</span>`;
    }
    
    clear() {
        this.container.innerHTML = '';
        this.lineCount = 0;
    }
    
    // 添加代码执行输出
    addCodeOutput(output, isError = false) {
        const type = isError ? 'error' : 'output';
        const lines = output.split('\n');
        
        lines.forEach(line => {
            if (line.trim()) {
                this.addOutput(line, type);
            }
        });
    }
}

// 全局终端实例
let terminal;

// 初始化终端
document.addEventListener('DOMContentLoaded', function() {
    const terminalOutput = document.getElementById('terminal-output');
    terminal = new TerminalOutput(terminalOutput);
    
    // 添加欢迎消息
    terminal.addOutput('AI开发环境已启动', 'success');
    terminal.addOutput('输入您的Python代码并点击运行按钮执行', 'info');
});

// 全局函数,供其他模块使用
function addTerminalOutput(message, type = 'info') {
    if (terminal) {
        terminal.addOutput(message, type);
    }
}

终端输出类管理所有控制台消息的显示。每条消息都包含时间戳和类型图标,使输出更加直观。消息类型包括信息、错误、警告和成功,每种类型使用不同的颜色和图标表示。

为了防止长时间运行导致的内存问题,终端限制了最大显示行数,超过限制时会自动删除最早的消息。自动滚动功能确保新消息始终可见。代码执行输出特别处理,将多行输出分割为单独的消息行,并区分标准输出和错误输出。

三、后端服务架构与实现

3.1 Flask应用基础架构

后端使用Flask框架构建,提供RESTful API和WebSocket支持:

from flask import Flask, request, jsonify, send_from_directory
from flask_socketio import SocketIO, emit
import os
import json
import subprocess
import sys
import threading
import time
from datetime import datetime
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('AI-IDE')

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
socketio = SocketIO(app, cors_allowed_origins="*")

# 项目根目录
PROJECT_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'workspace')
os.makedirs(PROJECT_ROOT, exist_ok=True)

class ProjectManager:
    """项目管理器,负责文件操作和项目配置"""
    
    def __init__(self, project_root):
        self.project_root = project_root
        self.current_file = None
        self.available_extensions = {
            '.py': 'python',
            '.js': 'javascript', 
            '.html': 'html',
            '.css': 'css',
            '.json': 'json',
            '.md': 'markdown',
            '.txt': 'plaintext'
        }
    
    def get_file_tree(self):
        """获取项目文件树"""
        file_tree = []
        
        def scan_directory(directory, relative_path=''):
            items = []
            try:
                for item in os.listdir(directory):
                    if item.startswith('.'):  # 忽略隐藏文件
                        continue
                        
                    item_path = os.path.join(directory, item)
                    relative_item_path = os.path.join(relative_path, item) if relative_path else item
                    
                    if os.path.isdir(item_path):
                        # 递归扫描子目录
                        children = scan_directory(item_path, relative_item_path)
                        items.append({
                            'name': item,
                            'path': relative_item_path,
                            'type': 'directory',
                            'children': children
                        })
                    else:
                        # 文件信息
                        _, ext = os.path.splitext(item)
                        items.append({
                            'name': item,
                            'path': relative_item_path,
                            'type': 'file',
                            'extension': ext.lower(),
                            'language': self.available_extensions.get(ext.lower(), 'plaintext'),
                            'size': os.path.getsize(item_path),
                            'modified': os.path.getmtime(item_path)
                        })
            except PermissionError:
                logger.warning(f"权限不足,无法访问目录: {directory}")
            except Exception as e:
                logger.error(f"扫描目录错误: {e}")
            
            return sorted(items, key=lambda x: (x['type'] != 'directory', x['name'].lower()))
        
        file_tree = scan_directory(self.project_root)
        return file_tree
    
    def get_file_content(self, filename):
        """获取文件内容"""
        try:
            filepath = os.path.join(self.project_root, filename)
            if not os.path.exists(filepath):
                return None
            
            with open(filepath, 'r', encoding='utf-8') as f:
                content = f.read()
            
            return {
                'filename': filename,
                'content': content,
                'size': len(content)
            }
        except Exception as e:
            logger.error(f"读取文件失败 {filename}: {e}")
            return None
    
    def save_file(self, filename, content):
        """保存文件"""
        try:
            filepath = os.path.join(self.project_root, filename)
            
            # 确保目录存在
            os.makedirs(os.path.dirname(filepath), exist_ok=True)
            
            with open(filepath, 'w', encoding='utf-8') as f:
                f.write(content)
            
            logger.info(f"文件保存成功: {filename}")
            return True
        except Exception as e:
            logger.error(f"保存文件失败 {filename}: {e}")
            return False
    
    def create_file(self, filename, content=''):
        """创建新文件"""
        try:
            filepath = os.path.join(self.project_root, filename)
            
            if os.path.exists(filepath):
                return False, "文件已存在"
            
            with open(filepath, 'w', encoding='utf-8') as f:
                f.write(content)
            
            logger.info(f"文件创建成功: {filename}")
            return True, "文件创建成功"
        except Exception as e:
            logger.error(f"创建文件失败 {filename}: {e}")
            return False, str(e)

# 初始化项目管理器
project_manager = ProjectManager(PROJECT_ROOT)

Flask应用设置了基础配置,包括密钥和跨域支持。项目管理器类封装了所有文件操作逻辑,包括获取文件树、读取文件内容、保存文件和创建新文件。

文件树扫描使用递归算法,遍历项目目录并构建层次结构。每个文件项包含名称、路径、类型、扩展名、语言类型、大小和修改时间等信息。文件操作都包含异常处理,确保在权限不足或其他错误情况下应用不会崩溃。

3.2 API路由设计

定义RESTful API接口,为前端提供数据服务:

# API路由
@app.route('/api/files', methods=['GET'])
def get_files():
    """获取文件树"""
    try:
        file_tree = project_manager.get_file_tree()
        return jsonify(file_tree)
    except Exception as e:
        logger.error(f"获取文件树失败: {e}")
        return jsonify({'error': str(e)}), 500

@app.route('/api/file/<path:filename>', methods=['GET'])
def get_file(filename):
    """获取文件内容"""
    try:
        file_content = project_manager.get_file_content(filename)
        if file_content:
            return jsonify(file_content)
        else:
            return jsonify({'error': '文件不存在或无法读取'}), 404
    except Exception as e:
        logger.error(f"获取文件内容失败 {filename}: {e}")
        return jsonify({'error': str(e)}), 500

@app.route('/api/save', methods=['POST'])
def save_file():
    """保存文件"""
    try:
        data = request.get_json()
        filename = data.get('filename')
        content = data.get('content')
        
        if not filename or content is None:
            return jsonify({'success': False, 'error': '文件名和内容不能为空'})
        
        success = project_manager.save_file(filename, content)
        
        if success:
            # 通过WebSocket通知文件已保存
            socketio.emit('file_saved', {
                'filename': filename,
                'timestamp': datetime.now().isoformat()
            })
            
            return jsonify({
                'success': True,
                'filename': filename,
                'message': '文件保存成功'
            })
        else:
            return jsonify({'success': False, 'error': '文件保存失败'})
            
    except Exception as e:
        logger.error(f"保存文件API错误: {e}")
        return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/api/file', methods=['POST'])
def create_file():
    """创建新文件"""
    try:
        data = request.get_json()
        filename = data.get('filename')
        content = data.get('content', '')
        
        if not filename:
            return jsonify({'success': False, 'error': '文件名不能为空'})
        
        success, message = project_manager.create_file(filename, content)
        
        return jsonify({
            'success': success,
            'message': message,
            'filename': filename if success else None
        })
    except Exception as e:
        logger.error(f"创建文件API错误: {e}")
        return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/api/run', methods=['POST'])
def run_code():
    """运行Python代码"""
    try:
        data = request.get_json()
        code = data.get('code', '')
        filename = data.get('filename', 'temp_script.py')
        
        if not code.strip():
            return jsonify({'success': False, 'error': '代码不能为空'})
        
        # 在单独的线程中执行代码,避免阻塞主线程
        thread = threading.Thread(
            target=execute_python_code,
            args=(code, filename, request.sid)
        )
        thread.daemon = True
        thread.start()
        
        return jsonify({
            'success': True,
            'message': '代码执行已开始'
        })
        
    except Exception as e:
        logger.error(f"运行代码API错误: {e}")
        return jsonify({'success': False, 'error': str(e)}), 500

# 静态文件服务
@app.route('/')
def index():
    return send_from_directory('.', 'index.html')

@app.route('/<path:path>')
def static_files(path):
    return send_from_directory('.', path)

API路由提供了完整的文件操作和代码执行功能。文件相关API包括获取文件树、读取文件内容、保存文件和创建新文件。每个端点都包含完善的错误处理,确保前端能够获得有意义的错误信息。

运行代码API接收Python代码并在单独的线程中执行,避免阻塞Web服务器。执行结果通过WebSocket实时推送到前端,实现类似真实IDE的交互体验。静态文件服务确保前端资源能够正确访问。

3.3 代码执行与WebSocket通信

代码执行是AI IDE的核心功能,需要安全地运行用户代码并实时返回结果:

def execute_python_code(code, filename, sid):
    """在子进程中执行Python代码并捕获输出"""
    try:
        # 保存临时文件
        temp_file = os.path.join(PROJECT_ROOT, f"temp_{int(time.time())}_{filename}")
        with open(temp_file, 'w', encoding='utf-8') as f:
            f.write(code)
        
        # 通知前端执行开始
        socketio.emit('execution_start', {
            'filename': filename,
            'timestamp': datetime.now().isoformat()
        }, room=sid)
        
        # 执行Python代码
        process = subprocess.Popen(
            [sys.executable, temp_file],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            bufsize=1,
            universal_newlines=True
        )
        
        # 实时读取输出
        def read_output(stream, output_type):
            for line in iter(stream.readline, ''):
                if line.strip():
                    socketio.emit('execution_output', {
                        'type': output_type,
                        'content': line.rstrip(),
                        'timestamp': datetime.now().isoformat()
                    }, room=sid)
            stream.close()
        
        # 启动输出读取线程
        stdout_thread = threading.Thread(target=read_output, args=(process.stdout, 'stdout'))
        stderr_thread = threading.Thread(target=read_output, args=(process.stderr, 'stderr'))
        
        stdout_thread.daemon = True
        stderr_thread.daemon = True
        
        stdout_thread.start()
        stderr_thread.start()
        
        # 等待进程结束
        return_code = process.wait()
        
        # 等待输出线程结束
        stdout_thread.join(timeout=1)
        stderr_thread.join(timeout=1)
        
        # 清理临时文件
        try:
            os.remove(temp_file)
        except:
            pass
        
        # 发送执行完成通知
        socketio.emit('execution_complete', {
            'return_code': return_code,
            'timestamp': datetime.now().isoformat()
        }, room=sid)
        
        logger.info(f"代码执行完成: {filename}, 返回码: {return_code}")
        
    except Exception as e:
        logger.error(f"代码执行错误: {e}")
        socketio.emit('execution_error', {
            'error': str(e),
            'timestamp': datetime.now().isoformat()
        }, room=sid)

# WebSocket事件处理
@socketio.on('connect')
def handle_connect():
    """客户端连接事件"""
    logger.info(f"客户端已连接: {request.sid}")
    emit('connection_established', {
        'message': '连接成功',
        'timestamp': datetime.now().isoformat()
    })

@socketio.on('disconnect')
def handle_disconnect():
    """客户端断开连接事件"""
    logger.info(f"客户端已断开: {request.sid}")

@socketio.on('terminal_input')
def handle_terminal_input(data):
    """处理终端输入(未来扩展)"""
    logger.info(f"终端输入: {data}")

代码执行函数创建子进程运行Python代码,并使用单独的线程实时捕获标准输出和错误输出。通过WebSocket将执行进度和输出实时推送到前端,实现真正的交互式编程体验。

临时文件用于保存当前执行的代码,执行完成后自动清理,避免积累垃圾文件。执行过程包含完整的异常处理,确保即使代码运行出错也不会影响IDE本身的稳定性。

WebSocket连接管理跟踪客户端连接状态,为每个客户端创建独立的通信通道。终端输入处理为未来扩展命令行交互功能预留了接口。

四、AI专用功能扩展

4.1 机器学习库自动补全

增强Monaco编辑器,为AI开发提供智能补全:

// AI代码补全提供器
class AICodeCompletionProvider {
    constructor() {
        this.setupCompletionItems();
    }
    
    setupCompletionItems() {
        // 常见的AI/ML库补全项
        this.completionItems = [
            // TensorFlow补全
            {
                label: 'import tensorflow as tf',
                kind: monaco.languages.CompletionItemKind.Snippet,
                documentation: '导入TensorFlow',
                insertText: 'import tensorflow as tf'
            },
            {
                label: 'tf.keras.Sequential',
                kind: monaco.languages.CompletionItemKind.Class,
                documentation: 'Keras顺序模型',
                insertText: 'tf.keras.Sequential([\n\t${1:layers}\n])'
            },
            {
                label: 'tf.keras.layers.Dense',
                kind: monaco.languages.CompletionItemKind.Class,
                documentation: '全连接层',
                insertText: 'tf.keras.layers.Dense(${1:units}, activation=\'${2:relu}\')'
            },
            
            // PyTorch补全
            {
                label: 'import torch',
                kind: monaco.languages.CompletionItemKind.Snippet,
                documentation: '导入PyTorch',
                insertText: 'import torch\nimport torch.nn as nn'
            },
            {
                label: 'nn.Module',
                kind: monaco.languages.CompletionItemKind.Class,
                documentation: 'PyTorch模型基类',
                insertText: 'class ${1:ModelName}(nn.Module):\n\tdef __init__(self):\n\t\tsuper().__init__()\n\t\t${2:# 层定义}\n\t\n\tdef forward(self, x):\n\t\t${3:# 前向传播}\n\t\treturn x'
            },
            
            // 数据处理补全
            {
                label: 'import pandas as pd',
                kind: monaco.languages.CompletionItemKind.Snippet,
                documentation: '导入Pandas',
                insertText: 'import pandas as pd'
            },
            {
                label: 'pd.read_csv',
                kind: monaco.languages.CompletionItemKind.Function,
                documentation: '读取CSV文件',
                insertText: 'pd.read_csv(\'${1:file_path}\')'
            },
            
            // 数据可视化补全
            {
                label: 'import matplotlib.pyplot as plt',
                kind: monaco.languages.CompletionItemKind.Snippet,
                documentation: '导入Matplotlib',
                insertText: 'import matplotlib.pyplot as plt'
            },
            {
                label: 'plt.plot',
                kind: monaco.languages.CompletionItemKind.Function,
                documentation: '绘制线图',
                insertText: 'plt.plot(${1:x}, ${2:y})\nplt.show()'
            },
            
            //  scikit-learn补全
            {
                label: 'from sklearn.model_selection import train_test_split',
                kind: monaco.languages.CompletionItemKind.Snippet,
                documentation: '导入训练测试分割',
                insertText: 'from sklearn.model_selection import train_test_split'
            },
            {
                label: 'train_test_split',
                kind: monaco.languages.CompletionItemKind.Function,
                documentation: '分割数据集',
                insertText: 'X_train, X_test, y_train, y_test = train_test_split(${1:X}, ${2:y}, test_size=${3:0.2})'
            }
        ];
    }
    
    provideCompletionItems(model, position) {
        const textUntilPosition = model.getValueInRange({
            startLineNumber: 1,
            startColumn: 1,
            endLineNumber: position.lineNumber,
            endColumn: position.column
        });
        
        // 根据上下文过滤补全项
        const lineContent = model.getLineContent(position.lineNumber);
        const word = model.getWordUntilPosition(position);
        
        const suggestions = this.completionItems.filter(item => 
            item.label.toLowerCase().includes(word.word.toLowerCase())
        );
        
        return {
            suggestions: suggestions.map(item => ({
                ...item,
                range: {
                    startLineNumber: position.lineNumber,
                    startColumn: word.startColumn,
                    endLineNumber: position.lineNumber,
                    endColumn: word.endColumn
                }
            }))
        };
    }
}

// 注册补全提供器
function registerAICompletion() {
    monaco.languages.registerCompletionItemProvider('python', {
        provideCompletionItems: (model, position) => {
            const provider = new AICodeCompletionProvider();
            return provider.provideCompletionItems(model, position);
        },
        triggerCharacters: ['.', ' ', '(']
    });
}

// 初始化时注册补全
require(['vs/editor/editor.main'], function() {
    // 等待编辑器初始化完成后注册补全
    setTimeout(registerAICompletion, 1000);
});

AI代码补全提供器为常见的机器学习库(TensorFlow、PyTorch、scikit-learn等)提供智能代码片段。每个补全项包含标签、类型、文档说明和插入文本,其中插入文本可以包含占位符,方便用户快速填充。

补全项根据当前光标位置的单词进行过滤,只显示相关的建议。触发字符包括点号、空格和左括号,覆盖了大多数需要代码补全的场景。这种上下文感知的补全大大提高了AI代码编写的效率。

4.2 模型训练可视化

添加训练过程可视化功能,实时显示损失和准确率曲线:

# 模型训练监控器
class TrainingMonitor:
    def __init__(self, socketio, sid):
        self.socketio = socketio
        self.sid = sid
        self.epoch_data = []
        
    def on_epoch_end(self, epoch, logs=None):
        """训练周期结束回调"""
        if logs is None:
            logs = {}
            
        epoch_data = {
            'epoch': epoch,
            'timestamp': datetime.now().isoformat(),
            **logs
        }
        self.epoch_data.append(epoch_data)
        
        # 发送训练进度更新
        self.socketio.emit('training_update', {
            'epoch': epoch,
            'logs': logs,
            'history': self.epoch_data
        }, room=self.sid)
        
    def on_train_begin(self, logs=None):
        """训练开始回调"""
        self.epoch_data = []
        self.socketio.emit('training_start', {
            'timestamp': datetime.now().isoformat()
        }, room=self.sid)
        
    def on_train_end(self, logs=None):
        """训练结束回调"""
        self.socketio.emit('training_end', {
            'timestamp': datetime.now().isoformat(),
            'final_logs': logs,
            'total_epochs': len(self.epoch_data)
        }, room=self.sid)

# 训练可视化API
@app.route('/api/train/monitor', methods=['GET'])
def get_training_monitor():
    """获取训练监控页面"""
    return """
    <!DOCTYPE html>
    <html>
    <head>
        <title>训练监控</title>
        <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
        <style>
            body { font-family: Arial, sans-serif; margin: 20px; }
            .chart-container { margin: 20px 0; }
            canvas { max-width: 100%; }
        </style>
    </head>
    <body>
        <h2>模型训练监控</h2>
        <div class="chart-container">
            <canvas id="lossChart"></canvas>
        </div>
        <div class="chart-container">
            <canvas id="accuracyChart"></canvas>
        </div>
        
        <script>
            const lossCtx = document.getElementById('lossChart').getContext('2d');
            const accuracyCtx = document.getElementById('accuracyChart').getContext('2d');
            
            const lossChart = new Chart(lossCtx, {
                type: 'line',
                data: {
                    labels: [],
                    datasets: [
                        {
                            label: '训练损失',
                            data: [],
                            borderColor: 'rgb(75, 192, 192)',
                            tension: 0.1
                        },
                        {
                            label: '验证损失',
                            data: [],
                            borderColor: 'rgb(255, 99, 132)',
                            tension: 0.1
                        }
                    ]
                },
                options: {
                    responsive: true,
                    plugins: {
                        title: {
                            display: true,
                            text: '损失曲线'
                        }
                    }
                }
            });
            
            const accuracyChart = new Chart(accuracyCtx, {
                type: 'line',
                data: {
                    labels: [],
                    datasets: [
                        {
                            label: '训练准确率',
                            data: [],
                            borderColor: 'rgb(75, 192, 192)',
                            tension: 0.1
                        },
                        {
                            label: '验证准确率',
                            data: [],
                            borderColor: 'rgb(255, 99, 132)',
                            tension: 0.1
                        }
                    ]
                },
                options: {
                    responsive: true,
                    plugins: {
                        title: {
                            display: true,
                            text: '准确率曲线'
                        }
                    },
                    scales: {
                        y: {
                            min: 0,
                            max: 1
                        }
                    }
                }
            });
            
            // WebSocket连接用于接收实时训练数据
            const socket = new WebSocket(`ws://${window.location.host}`);
            
            socket.onmessage = function(event) {
                const data = JSON.parse(event.data);
                
                if (data.type === 'training_update') {
                    const { epoch, logs, history } = data.data;
                    
                    // 更新损失图表
                    lossChart.data.labels = history.map(h => h.epoch);
                    lossChart.data.datasets[0].data = history.map(h => h.loss || h.train_loss);
                    lossChart.data.datasets[1].data = history.map(h => h.val_loss);
                    lossChart.update();
                    
                    // 更新准确率图表
                    if (logs.accuracy || logs.train_accuracy) {
                        accuracyChart.data.labels = history.map(h => h.epoch);
                        accuracyChart.data.datasets[0].data = history.map(h => h.accuracy || h.train_accuracy);
                        accuracyChart.data.datasets[1].data = history.map(h => h.val_accuracy);
                        accuracyChart.update();
                    }
                }
            };
        </script>
    </body>
    </html>
    """

训练监控器类实现了Keras回调接口,可以在模型训练过程中实时收集损失和准确率数据。每个训练周期结束时,通过WebSocket将最新数据推送到前端可视化界面。

前端使用Chart.js库创建交互式图表,实时显示训练损失和准确率曲线。图表自动更新,让用户能够直观地了解模型训练进度和性能变化。这种实时可视化对于调试模型和优化超参数非常有价值。

4.3 模型管理功能

添加模型保存和加载功能,完善AI工作流:

# 模型管理器
class ModelManager:
    def __init__(self, models_dir='saved_models'):
        self.models_dir = os.path.join(PROJECT_ROOT, models_dir)
        os.makedirs(self.models_dir, exist_ok=True)
    
    def save_model(self, model, model_name, metadata=None):
        """保存模型"""
        try:
            model_dir = os.path.join(self.models_dir, model_name)
            os.makedirs(model_dir, exist_ok=True)
            
            # 保存模型文件
            if hasattr(model, 'save'):
                # Keras/TensorFlow模型
                model_path = os.path.join(model_dir, 'model.h5')
                model.save(model_path)
            else:
                # PyTorch模型
                model_path = os.path.join(model_dir, 'model.pth')
                torch.save(model.state_dict(), model_path)
            
            # 保存元数据
            meta_path = os.path.join(model_dir, 'metadata.json')
            metadata = metadata or {}
            metadata.update({
                'saved_at': datetime.now().isoformat(),
                'model_type': type(model).__name__
            })
            
            with open(meta_path, 'w') as f:
                json.dump(metadata, f, indent=2)
            
            return True, f"模型保存成功: {model_path}"
            
        except Exception as e:
            return False, f"模型保存失败: {str(e)}"
    
    def load_model(self, model_name):
        """加载模型"""
        try:
            model_dir = os.path.join(self.models_dir, model_name)
            meta_path = os.path.join(model_dir, 'metadata.json')
            
            if not os.path.exists(meta_path):
                return False, "模型不存在"
            
            with open(meta_path, 'r') as f:
                metadata = json.load(f)
            
            return True, {
                'metadata': metadata,
                'model_dir': model_dir
            }
            
        except Exception as e:
            return False, f"模型加载失败: {str(e)}"
    
    def list_models(self):
        """列出所有保存的模型"""
        models = []
        
        for item in os.listdir(self.models_dir):
            model_dir = os.path.join(self.models_dir, item)
            meta_path = os.path.join(model_dir, 'metadata.json')
            
            if os.path.isdir(model_dir) and os.path.exists(meta_path):
                try:
                    with open(meta_path, 'r') as f:
                        metadata = json.load(f)
                    
                    models.append({
                        'name': item,
                        'metadata': metadata,
                        'created_at': metadata.get('saved_at', '未知')
                    })
                except:
                    continue
        
        return sorted(models, key=lambda x: x['created_at'], reverse=True)

# 初始化模型管理器
model_manager = ModelManager()

# 模型管理API
@app.route('/api/models', methods=['GET'])
def list_models():
    """获取模型列表"""
    try:
        models = model_manager.list_models()
        return jsonify(models)
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/models/save', methods=['POST'])
def save_model_api():
    """保存模型API"""
    try:
        data = request.get_json()
        model_code = data.get('model_code')
        model_name = data.get('model_name')
        metadata = data.get('metadata', {})
        
        # 在实际应用中,这里需要安全地执行模型代码
        # 这里简化为直接返回成功
        success, message = True, f"模型 {model_name} 保存成功"
        
        return jsonify({
            'success': success,
            'message': message
        })
        
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500

模型管理器提供了完整的模型生命周期管理功能,包括保存、加载和列表查看。保存模型时,除了模型权重文件,还会保存包含时间戳和模型类型的元数据文件。

模型列表API返回所有已保存模型的信息,方便用户管理和复用训练好的模型。在实际部署中,模型保存功能需要与代码执行环境深度集成,安全地序列化模型对象。

五、高级特性与优化

5.1 代码分析与质量检查

集成代码质量工具,提供实时反馈:

# 代码分析器
class CodeAnalyzer:
    def __init__(self):
        self.checks = [
            self.check_imports,
            self.check_model_definitions,
            self.check_data_loading,
            self.check_training_loops
        ]
    
    def analyze_code(self, code):
        """分析Python代码"""
        issues = []
        
        try:
            # 解析代码
            tree = ast.parse(code)
            
            # 执行各种检查
            for check in self.checks:
                issues.extend(check(tree))
                
        except SyntaxError as e:
            issues.append({
                'type': 'error',
                'message': f'语法错误: {e.msg}',
                'line': e.lineno,
                'severity': 'high'
            })
        
        return issues
    
    def check_imports(self, tree):
        """检查导入语句"""
        issues = []
        imports = []
        
        for node in ast.walk(tree):
            if isinstance(node, (ast.Import, ast.ImportFrom)):
                imports.append(node)
        
        # 检查是否缺少常见的AI库导入
        imported_modules = set()
        for imp in imports:
            if isinstance(imp, ast.Import):
                for name in imp.names:
                    imported_modules.add(name.name)
            elif isinstance(imp, ast.ImportFrom):
                if imp.module:
                    imported_modules.add(imp.module)
        
        ai_libraries = {'tensorflow', 'torch', 'sklearn', 'numpy', 'pandas'}
        missing_imports = ai_libraries - imported_modules
        
        if missing_imports:
            issues.append({
                'type': 'warning',
                'message': f'可能缺少AI库导入: {", ".join(missing_imports)}',
                'line': 1,
                'severity': 'low'
            })
        
        return issues
    
    def check_model_definitions(self, tree):
        """检查模型定义"""
        issues = []
        
        class_definitions = [node for node in ast.walk(tree) if isinstance(node, ast.ClassDef)]
        
        for class_def in class_definitions:
            # 检查是否继承自nn.Module (PyTorch)
            bases = [base.id for base in class_def.bases if isinstance(base, ast.Name)]
            if 'Module' in bases:
                # 检查是否有forward方法
                method_names = [node.name for node in class_def.body if isinstance(node, ast.FunctionDef)]
                if 'forward' not in method_names:
                    issues.append({
                        'type': 'error',
                        'message': f'PyTorch模型 {class_def.name} 缺少forward方法',
                        'line': class_def.lineno,
                        'severity': 'high'
                    })
        
        return issues
    
    def check_data_loading(self, tree):
        """检查数据加载代码"""
        issues = []
        
        function_calls = [node for node in ast.walk(tree) if isinstance(node, ast.Call)]
        
        data_loading_functions = {
            'read_csv', 'read_excel', 'load_data', 'DataLoader'
        }
        
        for call in function_calls:
            if isinstance(call.func, ast.Attribute):
                func_name = call.func.attr
            elif isinstance(call.func, ast.Name):
                func_name = call.func.id
            else:
                continue
            
            if func_name in data_loading_functions:
                # 检查是否有数据预处理
                issues.append({
                    'type': 'info',
                    'message': f'检测到数据加载函数 {func_name},请确保进行适当的数据预处理',
                    'line': call.lineno,
                    'severity': 'medium'
                })
        
        return issues
    
    def check_training_loops(self, tree):
        """检查训练循环"""
        issues = []
        
        for_loops = [node for node in ast.walk(tree) if isinstance(node, ast.For)]
        
        for loop in for_loops:
            # 简单的训练循环检测
            loop_code = ast.unparse(loop)
            if 'epoch' in loop_code or 'range' in loop_code:
                issues.append({
                    'type': 'info',
                    'message': '检测到可能的训练循环,建议添加进度监控和模型保存',
                    'line': loop.lineno,
                    'severity': 'medium'
                })
        
        return issues

# 代码分析API
@app.route('/api/analyze', methods=['POST'])
def analyze_code():
    """分析代码质量"""
    try:
        data = request.get_json()
        code = data.get('code', '')
        
        analyzer = CodeAnalyzer()
        issues = analyzer.analyze_code(code)
        
        return jsonify({
            'issues': issues,
            'total_issues': len(issues),
            'summary': {
                'errors': len([i for i in issues if i['severity'] == 'high']),
                'warnings': len([i for i in issues if i['severity'] == 'medium']),
                'info': len([i for i in issues if i['severity'] == 'low'])
            }
        })
        
    except Exception as e:
        return jsonify({'error': str(e)}), 500

代码分析器使用Python的抽象语法树(ast)模块解析代码结构,执行多种静态检查。检查内容包括导入语句分析、模型定义验证、数据加载代码检查和训练循环检测。

分析结果按照严重程度分类,高严重性问题表示可能影响代码运行的错误,中等严重性问题是需要关注的警告,低严重性问题提供改进建议。这种分层反馈帮助用户优先处理最重要的问题。

5.2 性能优化与缓存

添加缓存机制提升IDE响应速度:

# 缓存管理器
import functools
from typing import Any, Dict
import hashlib
import pickle

class CacheManager:
    def __init__(self, cache_dir='.cache'):
        self.cache_dir = os.path.join(PROJECT_ROOT, cache_dir)
        os.makedirs(self.cache_dir, exist_ok=True)
        self.memory_cache: Dict[str, Any] = {}
    
    def get_cache_key(self, func_name: str, *args, **kwargs) -> str:
        """生成缓存键"""
        key_data = {
            'func': func_name,
            'args': args,
            'kwargs': kwargs
        }
        key_string = pickle.dumps(key_data)
        return hashlib.md5(key_string).hexdigest()
    
    def get_cache_path(self, key: str) -> str:
        """获取缓存文件路径"""
        return os.path.join(self.cache_dir, f"{key}.pkl")
    
    def cached(self, expire_seconds=3600):
        """缓存装饰器"""
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                # 生成缓存键
                cache_key = self.get_cache_key(func.__name__, *args, **kwargs)
                
                # 检查内存缓存
                if cache_key in self.memory_cache:
                    return self.memory_cache[cache_key]
                
                # 检查磁盘缓存
                cache_path = self.get_cache_path(cache_key)
                if os.path.exists(cache_path):
                    file_age = time.time() - os.path.getmtime(cache_path)
                    if file_age < expire_seconds:
                        try:
                            with open(cache_path, 'rb') as f:
                                result = pickle.load(f)
                                self.memory_cache[cache_key] = result
                                return result
                        except:
                            pass
                
                # 执行函数并缓存结果
                result = func(*args, **kwargs)
                
                # 更新内存缓存
                self.memory_cache[cache_key] = result
                
                # 更新磁盘缓存
                try:
                    with open(cache_path, 'wb') as f:
                        pickle.dump(result, f)
                except:
                    pass
                
                return result
            return wrapper
        return decorator

# 初始化缓存管理器
cache_manager = CacheManager()

# 应用缓存的API
@cache_manager.cached(expire_seconds=300)  # 5分钟缓存
@app.route('/api/files/cached', methods=['GET'])
def get_files_cached():
    """带缓存的文件树API"""
    return get_files()

@cache_manager.cached(expire_seconds=60)  # 1分钟缓存  
@app.route('/api/models/cached', methods=['GET'])
def list_models_cached():
    """带缓存的模型列表API"""
    return list_models()

缓存管理器实现了两级缓存系统:内存缓存用于快速访问,磁盘缓存用于持久化存储。缓存键基于函数名称和参数生成,确保不同调用之间的隔离。

缓存装饰器可以轻松应用到任何函数,自动处理缓存的读取、写入和过期检查。对于文件树和模型列表这种不经常变化但频繁访问的数据,缓存可以显著提升响应速度。

5.3 安全增强

添加基本的安全措施保护IDE环境:

# 安全管理器
class SecurityManager:
    def __init__(self):
        self.allowed_imports = {
            'numpy', 'pandas', 'matplotlib', 'sklearn',
            'tensorflow', 'torch', 'keras', 'seaborn',
            'plotly', 'PIL', 'opencv', 'requests'
        }
        
        self.blocked_commands = {
            'os.system', 'subprocess.call', 'eval', 'exec',
            'open', '__import__', 'compile'
        }
    
    def check_code_safety(self, code: str) -> Dict[str, Any]:
        """检查代码安全性"""
        issues = []
        
        try:
            tree = ast.parse(code)
            
            # 检查危险的函数调用
            for node in ast.walk(tree):
                if isinstance(node, ast.Call):
                    func_name = self.get_function_name(node.func)
                    if func_name in self.blocked_commands:
                        issues.append({
                            'type': 'security',
                            'message': f'检测到可能危险的函数调用: {func_name}',
                            'line': node.lineno,
                            'severity': 'high'
                        })
                
                # 检查导入语句
                elif isinstance(node, (ast.Import, ast.ImportFrom)):
                    imports = self.get_imports(node)
                    for imp in imports:
                        if imp not in self.allowed_imports:
                            issues.append({
                                'type': 'security',
                                'message': f'不允许的导入: {imp}',
                                'line': node.lineno,
                                'severity': 'high'
                            })
        
        except SyntaxError:
            # 语法错误由代码分析器处理
            pass
        
        return {
            'safe': len(issues) == 0,
            'issues': issues
        }
    
    def get_function_name(self, node) -> str:
        """获取函数名称"""
        if isinstance(node, ast.Name):
            return node.id
        elif isinstance(node, ast.Attribute):
            return f"{self.get_function_name(node.value)}.{node.attr}"
        return "unknown"
    
    def get_imports(self, node) -> List[str]:
        """获取导入的模块名称"""
        imports = []
        
        if isinstance(node, ast.Import):
            for name in node.names:
                imports.append(name.name.split('.')[0])  # 只取主模块名
        elif isinstance(node, ast.ImportFrom):
            if node.module:
                imports.append(node.module.split('.')[0])
        
        return imports

# 初始化安全管理器
security_manager = SecurityManager()

# 安全的代码执行API
@app.route('/api/run/safe', methods=['POST'])
def run_code_safe():
    """安全地运行代码"""
    try:
        data = request.get_json()
        code = data.get('code', '')
        
        # 安全检查
        safety_check = security_manager.check_code_safety(code)
        if not safety_check['safe']:
            return jsonify({
                'success': False,
                'error': '代码安全检查失败',
                'issues': safety_check['issues']
            })
        
        # 如果安全,继续执行
        return run_code()
        
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500

安全管理器通过静态代码分析检测潜在的安全风险,包括危险的函数调用和不允许的模块导入。安全检查在代码执行前进行,阻止可能危害系统的操作。

允许的导入列表包含了常见的AI和数据科学库,同时阻止了系统操作和代码执行相关函数。这种白名单方法在安全性和功能性之间取得了平衡,确保IDE环境既安全又实用。

六、部署与生产环境配置

6.1 应用启动配置

创建完整的主程序文件,整合所有组件:

# main.py - 应用主入口
import os
import sys
import argparse
from threading import Thread

def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='AI开发环境IDE')
    parser.add_argument('--host', default='127.0.0.1', help='绑定主机地址')
    parser.add_argument('--port', type=int, default=5000, help='绑定端口')
    parser.add_argument('--debug', action='store_true', help='调试模式')
    parser.add_argument('--workspace', default='workspace', help='工作区目录')
    
    args = parser.parse_args()
    
    # 设置工作区目录
    global PROJECT_ROOT
    PROJECT_ROOT = os.path.abspath(args.workspace)
    os.makedirs(PROJECT_ROOT, exist_ok=True)
    
    print(f"AI开发环境启动中...")
    print(f"工作区目录: {PROJECT_ROOT}")
    print(f"访问地址: http://{args.host}:{args.port}")
    print(f"调试模式: {args.debug}")
    
    # 启动Flask应用
    try:
        socketio.run(
            app, 
            host=args.host, 
            port=args.port,
            debug=args.debug,
            allow_unsafe_werkzeug=True
        )
    except KeyboardInterrupt:
        print("\n正在关闭AI开发环境...")
    except Exception as e:
        print(f"启动失败: {e}")
        sys.exit(1)

if __name__ == '__main__':
    main()

主程序提供命令行参数配置,包括主机地址、端口、调试模式和工作区目录。清晰的启动信息帮助用户了解应用状态和访问方式。

6.2 生产环境部署

创建生产环境部署配置:

# deployment.py - 生产环境部署配置
import os
import logging
from logging.handlers import RotatingFileHandler

def setup_production_logging():
    """设置生产环境日志"""
    log_dir = 'logs'
    os.makedirs(log_dir, exist_ok=True)
    
    # 主应用日志
    app_handler = RotatingFileHandler(
        os.path.join(log_dir, 'ai_ide.log'),
        maxBytes=10*1024*1024,  # 10MB
        backupCount=5
    )
    app_handler.setLevel(logging.INFO)
    app_handler.setFormatter(logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    ))
    
    # 错误日志
    error_handler = RotatingFileHandler(
        os.path.join(log_dir, 'errors.log'),
        maxBytes=10*1024*1024,
        backupCount=5
    )
    error_handler.setLevel(logging.ERROR)
    error_handler.setFormatter(logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(pathname)s:%(lineno)d - %(message)s'
    ))
    
    # 配置根日志器
    logging.basicConfig(
        level=logging.INFO,
        handlers=[app_handler, error_handler]
    )

class ProductionConfig:
    """生产环境配置"""
    SECRET_KEY = os.environ.get('SECRET_KEY', 'production-secret-key-change-in-production')
    DEBUG = False
    TESTING = False
    
    # 安全配置
    SESSION_COOKIE_SECURE = True
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SAMESITE = 'Lax'
    
    # 上传限制
    MAX_CONTENT_LENGTH = 16 * 1024 * 1024  # 16MB

def check_dependencies():
    """检查依赖环境"""
    required_packages = [
        'flask', 'flask_socketio', 'numpy', 'pandas'
    ]
    
    missing_packages = []
    
    for package in required_packages:
        try:
            __import__(package)
        except ImportError:
            missing_packages.append(package)
    
    if missing_packages:
        print("缺少必要的依赖包:")
        for package in missing_packages:
            print(f"  - {package}")
        print("\n请使用以下命令安装:")
        print(f"pip install {' '.join(missing_packages)}")
        return False
    
    return True

if __name__ == '__main__':
    if check_dependencies():
        print("环境检查通过,可以部署生产环境")
        
        # 应用生产配置
        app.config.from_object(ProductionConfig())
        setup_production_logging()
        
        print("生产环境配置完成")
    else:
        print("环境检查失败,请先安装缺失的依赖")

生产环境配置包括日志管理、安全设置和依赖检查。日志使用轮转文件处理器,避免日志文件无限增长。安全配置启用Cookie安全标志,防止常见的Web攻击。

依赖检查确保运行环境包含所有必要的Python包,提供清晰的安装指导。这种预防性检查可以避免在生产环境部署时出现意外的导入错误。

结论:构建面向未来的AI开发平台

通过本文的详细实现,我们成功构建了一个功能完整的AI专用IDE,具备代码编辑、文件管理、代码执行、模型训练可视化等核心功能。这个基于Python和HTML的解决方案展示了现代Web技术在AI开发工具中的应用潜力。

技术亮点总结

  1. 现代化前端架构:使用Monaco Editor提供专业的代码编辑体验,配合响应式设计确保跨设备兼容性。

  2. 实时交互能力:通过WebSocket实现代码执行进度和输出的实时推送,创造流畅的开发体验。

  3. AI专用功能:集成了机器学习库代码补全、训练过程可视化、模型管理等AI开发专属功能。

  4. 安全性与稳定性:代码安全检查、缓存机制和完整的错误处理确保系统稳定安全运行。

  5. 可扩展架构:模块化设计使得新功能可以轻松集成,插件系统为未来扩展预留了空间。

实际应用价值

这个AI IDE不仅是一个技术演示,更是具有实际应用价值的开发工具。它可以用于:

  • 教育领域:为机器学习初学者提供友好的学习环境
  • 原型开发:快速验证AI算法想法和概念
  • 代码演示:交互式展示机器学习代码示例
  • 团队协作:共享开发环境和项目配置

未来发展方向

随着AI技术的不断发展,这个IDE平台可以进一步扩展:

  1. 云端集成:对接云GPU资源,提供强大的计算能力
  2. 协作功能:实现实时多人协作编程
  3. 自动化ML:集成AutoML功能,自动化模型选择和超参数调优
  4. 模型部署:添加一键部署功能,将训练好的模型部署到生产环境

通过持续迭代和功能增强,这个基于Python和HTML的AI IDE有望成为AI开发者的重要工具,推动人工智能技术的普及和发展。


参考资源

  1. Monaco Editor官方文档
  2. Flask Web开发指南
  3. WebSocket实时通信协议
  4. Python AST模块文档
  5. Chart.js数据可视化库

这个完整的AI IDE实现展示了现代Web技术与AI开发工作流的深度结合,为构建更智能、更高效的开发环境提供了可行的技术方案。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐