Просмотр исходного кода

fix: top 3 plugin system issues

1. Cleaned 25+ verbose console.log statements from production code
2. execute_plugin: removed trailing '---\nNull' output
   - Skip Null return values from Symja evaluation
   - Separator only added when there is multiple output lines
3. SymjaJsApi: added series(), together(), renderLatex() methods
   (now matches WebView IJS.Symja API)

Co-Authored-By: Claude <noreply@anthropic.com>
nonvioce 3 месяцев назад
Родитель
Сommit
a3d036aa7e

+ 1 - 1
app/src/main/assets/console.html

@@ -372,7 +372,7 @@
             </div>
             <div class="setting-info">
                 <p>某人的计算器 v1.3</p>
-                <p>Symja 3.1.0 · MCP 2024-11-05</p>
+                <p>Symja 3.1.0 · MCP 2025-03-26 · 发布于 2026-07-19</p>
             </div>
         </div>
     </div>

+ 2532 - 2532
app/src/main/assets/console.js

@@ -1,2532 +1,2532 @@
-// 控制台交互逻辑
-class SymjaConsole {
-    constructor() {
-        this.output = document.getElementById('output');
-        this.input = document.getElementById('input');
-        this.inputPrompt = document.getElementById('inputPrompt');
-        this.completionList = document.getElementById('completionList');
-        this.helpBtn = document.getElementById('helpBtn');
-        this.pluginsBtn = document.getElementById('pluginsBtn');
-        this.settingsBtn = document.getElementById('settingsBtn');
-        
-        // 历史记录
-        this.history = this.loadHistory();
-        this.historyIndex = this.history.length;
-        this.tempInput = '';
-        
-        // 插件相关
-        this.plugins = [];
-        this.currentPlugin = null;
-        
-        // 自动补全相关
-        this.completions = [];
-        this.completionIndex = -1;
-        
-        // 变量追踪
-        this.variables = new Set();
-        
-        // 执行计数器
-        this.executionCount = this.loadExecutionCount();
-
-        this.init();
-        this.loadPlugins();
-        this.refreshPluginList(); // 初始化时刷新插件列表
-        this.showWelcome();
-        this.updatePrompt();
-    }
-
-    init() {
-        // 输入框事件
-        this.input.addEventListener('keydown', (e) => this.handleKeyDown(e));
-        this.input.addEventListener('input', () => this.handleInput());
-
-        // 发送按钮
-        const sendBtn = document.getElementById('sendBtn');
-        if (sendBtn) {
-            sendBtn.addEventListener('click', () => this.evaluate());
-        }
-
-        // 顶部导航按钮
-        this.helpBtn?.addEventListener('click', () => this.showHelp());
-        this.pluginsBtn?.addEventListener('click', () => this.showPluginPanel());
-        this.settingsBtn?.addEventListener('click', () => this.showSettings());
-
-        // 设置面板
-        document.getElementById('closeSettings')?.addEventListener('click', () => this.hideSettings());
-        document.getElementById('applySettings')?.addEventListener('click', () => this.applySettings());
-
-        // 加载已保存的设置
-        this.loadSettings();
-
-        // MCP 事件轮询(仅在服务器开启时运行)
-        this.mcpEventCount = 0;
-        this.mcpPollTimer = null;
-        this.startMcpPollingIfNeeded();
-        
-        // 快捷键工具栏
-        this.initShortcutsBar();
-        
-        // 帮助页面关闭按钮
-        document.getElementById('closeHelp')?.addEventListener('click', () => {
-            this.hideHelp();
-        });
-
-        // 恢复默认插件
-        document.getElementById('restorePluginsBtn')?.addEventListener('click', () => {
-            try {
-                const result = Android.restoreDefaultPlugins();
-                this.loadPlugins();
-                this.refreshPluginList();
-                this.addOutput(result, 'help');
-                this.scrollToBottom();
-            } catch(e) {
-                this.addOutput('❌ 恢复失败: ' + e.message, 'error');
-            }
-        });
-
-        // 全屏面板关闭
-        document.getElementById('closeFullscreen')?.addEventListener('click', () => {
-            const fs = document.getElementById('fullscreenPanel');
-            fs.classList.remove('show');
-            // 调用插件的 IJS.ui.close() 进行清理(停止音乐、动画等)
-            if (this._lastIjsApi && this._lastIjsApi.ui) {
-                this._lastIjsApi.ui.close();
-            }
-            // 确保动画停止
-            if (this._lastIjsApi && this._lastIjsApi.canvas) {
-                this._lastIjsApi.canvas.stop();
-            }
-        });
-
-        // 插件面板事件
-        document.getElementById('closePluginPanel')?.addEventListener('click', () => {
-            this.hidePluginPanel();
-        });
-
-        document.getElementById('closePluginDialog')?.addEventListener('click', () => {
-            this.hidePluginDialog();
-        });
-
-        document.getElementById('cancelPlugin')?.addEventListener('click', () => {
-            this.hidePluginDialog();
-        });
-
-        document.getElementById('executePlugin')?.addEventListener('click', () => {
-            this.executeCurrentPlugin();
-        });
-    }
-
-    initShortcutsBar() {
-        const buttons = document.querySelectorAll('.shortcut-btn');
-        buttons.forEach(btn => {
-            btn.addEventListener('click', (e) => {
-                e.preventDefault();
-                const action = btn.dataset.action;
-                const value = btn.dataset.value;
-                
-                switch(action) {
-                    case 'bracket':
-                    case 'insert':
-                        this.insertText(value);
-                        break;
-                    case 'history-up':
-                        this.navigateHistory(-1);
-                        break;
-                    case 'history-down':
-                        this.navigateHistory(1);
-                        break;
-                }
-                
-                // 保持输入框焦点
-                this.input.focus();
-            });
-        });
-        
-        // 初始化插件标签页
-        this.initPluginTabs();
-        
-        // 初始化插件分享功能
-        this.initPluginShare();
-    }
-
-    initPluginTabs() {
-        const tabs = document.querySelectorAll('.plugin-tab');
-        const contents = document.querySelectorAll('.tab-content');
-        
-        tabs.forEach(tab => {
-            tab.addEventListener('click', () => {
-                const targetTab = tab.dataset.tab;
-                
-                // 切换标签激活状态
-                tabs.forEach(t => t.classList.remove('active'));
-                tab.classList.add('active');
-                
-                // 切换内容显示
-                contents.forEach(content => {
-                    if (content.dataset.content === targetTab) {
-                        content.classList.add('active');
-                    } else {
-                        content.classList.remove('active');
-                    }
-                });
-                
-                // 如果切换到导入/分享标签,更新插件列表
-                if (targetTab === 'import') {
-                    this.updatePluginSelects();
-                }
-            });
-        });
-    }
-
-    initPluginShare() {
-        // 使用事件委托,在document上监听点击事件
-        document.addEventListener('click', (e) => {
-            const target = e.target;
-            
-            // 生成分享码
-            if (target.id === 'generateShareCode') {
-                console.log('generateShareCode clicked');
-                e.preventDefault();
-                this.generateShareCode();
-            }
-            
-            // 复制分享码
-            if (target.id === 'copyShareCode') {
-                console.log('copyShareCode clicked');
-                e.preventDefault();
-                this.copyShareCode();
-            }
-            
-            // 导入插件
-            if (target.id === 'importPlugin') {
-                console.log('importPlugin clicked');
-                e.preventDefault();
-                this.importPlugin();
-            }
-            
-            // 删除插件
-            if (target.id === 'deletePlugin') {
-                console.log('deletePlugin clicked');
-                e.preventDefault();
-                this.deletePluginConfirm();
-            }
-        });
-    }
-
-    updatePluginSelects() {
-        const shareSelect = document.getElementById('sharePluginSelect');
-        const deleteSelect = document.getElementById('deletePluginSelect');
-        
-        if (shareSelect) {
-            shareSelect.innerHTML = '<option value="">选择插件...</option>';
-            this.plugins.forEach((plugin, index) => {
-                const option = document.createElement('option');
-                option.value = index;
-                option.textContent = `${plugin.icon} ${plugin.name}`;
-                shareSelect.appendChild(option);
-            });
-        }
-        
-        if (deleteSelect) {
-            deleteSelect.innerHTML = '<option value="">选择要删除的插件...</option>';
-            this.plugins.forEach((plugin, index) => {
-                const option = document.createElement('option');
-                option.value = index;
-                option.textContent = `${plugin.icon} ${plugin.name}`;
-                deleteSelect.appendChild(option);
-            });
-        }
-    }
-
-    generateShareCode() {
-        const select = document.getElementById('sharePluginSelect');
-        const output = document.getElementById('shareCodeOutput');
-        const copyBtn = document.getElementById('copyShareCode');
-        
-        if (!select || !output) return;
-        
-        const index = parseInt(select.value);
-        if (isNaN(index) || index < 0 || index >= this.plugins.length) {
-            this.addOutput('❌ 请先选择一个插件', 'error');
-            this.scrollToBottom();
-            return;
-        }
-        
-        const plugin = this.plugins[index];
-        const shareCode = JSON.stringify(plugin, null, 2);
-        
-        output.value = shareCode;
-        if (copyBtn) copyBtn.style.display = 'block';
-        
-        this.addOutput(`✅ 已生成 "${plugin.name}" 的分享码`, 'help');
-        this.scrollToBottom();
-    }
-
-    copyShareCode() {
-        const output = document.getElementById('shareCodeOutput');
-        if (!output || !output.value) return;
-        
-        output.select();
-        document.execCommand('copy');
-        
-        this.addOutput('📋 分享码已复制到剪贴板', 'help');
-        this.scrollToBottom();
-    }
-
-    importPlugin() {
-        const input = document.getElementById('importCodeInput');
-        if (!input || !input.value.trim()) {
-            this.addOutput('❌ 请粘贴插件代码', 'error');
-            this.scrollToBottom();
-            return;
-        }
-        
-        try {
-            const pluginData = JSON.parse(input.value);
-            
-            // 验证插件格式
-            if (!pluginData.id || !pluginData.name || !pluginData.jsCode) {
-                throw new Error('插件格式不正确,缺少必要字段');
-            }
-            
-            // 调用 Android 接口保存插件
-            try {
-                const result = Android.savePlugin(JSON.stringify(pluginData));
-                
-                // 重新加载插件
-                this.loadPlugins();
-                this.updatePluginSelects();
-                
-                // 刷新插件列表显示
-                this.refreshPluginList();
-                
-                // 显示成功消息
-                this.addOutput(`✅ ${result}`, 'help');
-                this.addOutput(`📦 插件 "${pluginData.name}" 已添加到列表`, 'help');
-                
-                // 清空输入
-                input.value = '';
-            } catch (e) {
-                this.addOutput(`❌ 保存失败: ${e.message}`, 'error');
-            }
-            
-            this.scrollToBottom();
-        } catch (e) {
-            this.addOutput(`❌ 解析失败: ${e.message}`, 'error');
-            this.scrollToBottom();
-        }
-    }
-
-    deletePluginConfirm() {
-        const select = document.getElementById('deletePluginSelect');
-        if (!select) {
-            console.error('deletePluginSelect element not found');
-            return;
-        }
-        
-        console.log('Select value:', select.value);
-        console.log('Plugins length:', this.plugins.length);
-        
-        const index = parseInt(select.value);
-        if (isNaN(index) || index < 0 || index >= this.plugins.length) {
-            this.addOutput('❌ 请先选择要删除的插件', 'error');
-            this.scrollToBottom();
-            return;
-        }
-        
-        const plugin = this.plugins[index];
-        console.log('Deleting plugin:', plugin.name, 'ID:', plugin.id);
-        
-        // 使用自定义确认对话框或直接删除
-        const confirmMessage = `确定要删除插件 "${plugin.name}" 吗?`;
-        console.log('Showing confirm dialog:', confirmMessage);
-        
-        let userConfirmed = false;
-        try {
-            userConfirmed = confirm(confirmMessage);
-            console.log('User confirmed:', userConfirmed);
-        } catch (e) {
-            console.error('Confirm dialog error:', e);
-            // 如果confirm失败,直接删除
-            userConfirmed = true;
-        }
-        
-        if (userConfirmed) {
-            console.log('Proceeding with deletion...');
-            try {
-                const result = Android.deletePlugin(plugin.id);
-                console.log('Delete result:', result);
-                this.addOutput(`✅ ${result}`, 'help');
-                
-                // 重新加载插件
-                this.loadPlugins();
-                this.updatePluginSelects();
-                
-                // 刷新插件列表显示
-                this.refreshPluginList();
-            } catch (e) {
-                console.error('Delete error:', e);
-                this.addOutput(`❌ 删除失败: ${e.message}`, 'error');
-            }
-            
-            this.scrollToBottom();
-        } else {
-            console.log('User cancelled deletion');
-        }
-    }
-
-    refreshPluginList() {
-        const list = document.getElementById('pluginList');
-        console.log('refreshPluginList called, list element:', list);
-        console.log('Current plugins count:', this.plugins.length);
-        
-        if (!list) return;
-        
-        list.innerHTML = '';
-        
-        if (this.plugins.length === 0) {
-            console.log('No plugins, showing empty message');
-            list.innerHTML = '<div class="no-plugins">暂无插件</div>';
-        } else {
-            console.log('Rendering', this.plugins.length, 'plugins');
-            this.plugins.forEach(plugin => {
-                const item = document.createElement('div');
-                item.className = 'plugin-item';
-                const commandInfo = plugin.command ? `<div class="plugin-command">命令: ${plugin.command}</div>` : '';
-                item.innerHTML = `
-                    <div class="plugin-icon">${plugin.icon}</div>
-                    <div class="plugin-info">
-                        <div class="plugin-name">${plugin.name}</div>
-                        <div class="plugin-desc">${plugin.description}</div>
-                        ${commandInfo}
-                    </div>
-                `;
-                item.addEventListener('click', () => this.openPlugin(plugin));
-                list.appendChild(item);
-            });
-            console.log('Finished rendering plugins, list children:', list.children.length);
-        }
-    }
-
-    insertText(text) {
-        const start = this.input.selectionStart;
-        const end = this.input.selectionEnd;
-        const value = this.input.value;
-        
-        // 插入文本
-        this.input.value = value.substring(0, start) + text + value.substring(end);
-        
-        // 设置光标位置
-        const newPos = start + text.length;
-        this.input.setSelectionRange(newPos, newPos);
-        
-        // 触发输入事件
-        this.handleInput();
-    }
-
-    handleKeyDown(e) {
-        if (e.key === 'Enter') {
-            e.preventDefault();
-            this.hideCompletionList();
-            this.evaluate();
-        } else if (e.key === 'ArrowUp') {
-            e.preventDefault();
-            this.hideCompletionList();
-            this.navigateHistory(-1);
-        } else if (e.key === 'ArrowDown') {
-            e.preventDefault();
-            this.hideCompletionList();
-            this.navigateHistory(1);
-        } else if (e.key === 'Escape') {
-            this.input.value = '';
-            this.completions = [];
-            this.completionIndex = -1;
-            this.hideCompletionList();
-        }
-    }
-
-    handleInput() {
-        // 更新自动补全列表
-        console.log('handleInput called, value:', this.input.value);
-        this.updateCompletionList();
-    }
-
-    showWelcome() {
-        const line = document.createElement('div');
-        line.className = 'terminal-line';
-        line.innerHTML = `<div class="welcome-message">
-            <h2>🧮 某人的计算器 v1.3</h2>
-            <p>基于 Symja 的交互式符号计算控制台</p>
-            <p><strong>快速开始:</strong></p>
-            <p><code>2+2</code> 算术运算</p>
-            <p><code>D[x^3, x]</code> 求导</p>
-            <p><code>Integrate[x^2, {x,0,1}]</code> 定积分</p>
-            <p><code>Solve[x^2-5*x+6==0, x]</code> 解方程</p>
-            <p><code>Plot[Sin[x], {x,0,2*Pi}]</code> 函数绘图</p>
-            <p style="margin-top:12px">输入 <code>help</code> 查看完整文档 · 输入 <code>plugins</code> 管理插件</p>
-        </div>`;
-        this.output.appendChild(line);
-    }
-
-    evaluate() {
-        let expression = this.input.value.trim();
-
-        if (!expression) {
-            return;
-        }
-
-        // 处理特殊命令
-        if (this.handleSpecialCommand(expression)) {
-            this.addToHistory(expression);
-            this.input.value = '';
-            return;
-        }
-
-        // 添加到历史记录
-        this.addToHistory(expression);
-
-        // 显示输入(带计数器)
-        this.addOutput(expression, 'input', { count: this.executionCount });
-
-        // 调用Android接口进行计算
-        try {
-            const result = Android.evaluatePlot(expression);
-
-            // 检测是否是 Graphics/绘图结果
-            if (typeof result === 'string' && result.startsWith('{"type":"plot"')) {
-                this.addPlotOutput(result, this.executionCount);
-            }
-            // LaTeX 渲染
-            else if (result.startsWith('##TEXFORM##')) {
-                const latex = result.substring(11);
-                this.addOutput(latex, 'result', { renderLatex: true, count: this.executionCount });
-                this.scrollToBottom();
-            }
-            // 普通文本结果
-            else {
-                this.addOutput(result, 'result', { count: this.executionCount });
-            }
-
-            // 追踪变量
-            this.trackVariables(expression);
-        } catch (e) {
-            this.addOutput('错误: 无法连接到计算引擎', 'error');
-        }
-
-        // 增加计数器
-        this.executionCount++;
-        this.saveExecutionCount();
-        this.updatePrompt();
-
-        // 清空输入框
-        this.input.value = '';
-        this.completions = [];
-        this.completionIndex = -1;
-
-        // 滚动到底部
-        this.scrollToBottom();
-    }
-
-    addPlotOutput(plotJson, count, labels) {
-        const data = JSON.parse(plotJson);
-        const line = document.createElement('div');
-        line.className = 'terminal-line';
-
-        const container = document.createElement('div');
-        container.className = 'plot-container';
-
-        const canvas = document.createElement('canvas');
-        canvas.className = 'plot-canvas';
-        const dpr = window.devicePixelRatio || 1;
-        canvas.width = 500 * dpr;
-        canvas.height = 500 * dpr;
-        canvas.style.width = '100%';
-        canvas.style.height = '100%';
-        container.appendChild(canvas);
-
-        // 长按保存(单指静止800ms触发)
-        let longPressTimer;
-        canvas.addEventListener('touchstart', (e) => {
-            clearTimeout(longPressTimer);
-            if (e.touches.length === 1) {
-                longPressTimer = setTimeout(() => this.savePlot(canvas), 800);
-            }
-        });
-        canvas.addEventListener('touchend', () => clearTimeout(longPressTimer));
-        canvas.addEventListener('touchmove', () => clearTimeout(longPressTimer));
-
-        line.appendChild(container);
-        this.output.appendChild(line);
-
-        if (!this.plotRenderer) {
-            this.plotRenderer = new PlotRenderer();
-        }
-        this.plotRenderer.render(canvas, data);
-    }
-
-    buildLegend(data, labels) {
-        const div = document.createElement('div');
-        div.className = 'plot-legend';
-        const colors = [
-            [0.369,0.507,0.710],[0.851,0.208,0.125],[0.267,0.612,0.329],
-            [0.737,0.349,0.800],[1.0,0.608,0.0]
-        ];
-        data.elements.forEach((el, i) => {
-            const c = el.color || colors[i % colors.length];
-            const rgb = `rgb(${Math.round(c[0]*255)},${Math.round(c[1]*255)},${Math.round(c[2]*255)})`;
-            const item = document.createElement('span');
-            item.className = 'legend-item';
-            item.innerHTML = `<span class="legend-swatch" style="background:${rgb}"></span>${labels ? labels[i] : '曲线'+(i+1)}`;
-            div.appendChild(item);
-        });
-        return div;
-    }
-
-    savePlot(canvas) {
-        try {
-            const dataUrl = canvas.toDataURL('image/png');
-            const base64 = dataUrl.substring(dataUrl.indexOf(',') + 1);
-            const filename = 'IJS_Plot_' + new Date().toISOString().replace(/[:.]/g, '-') + '.png';
-            const result = Android.savePlotImage(base64, filename);
-            this.addOutput(result, 'help');
-            this.scrollToBottom();
-        } catch (e) {
-            this.addOutput('保存失败: ' + e.message, 'error');
-            this.scrollToBottom();
-        }
-    }
-
-    handleSpecialCommand(cmd) {
-        const lower = cmd.toLowerCase();
-        
-        if (lower === 'help' || lower === '?') {
-            this.showHelp();
-            return true;
-        }
-        if (lower === 'clear' || lower === 'cls') {
-            this.clear();
-            return true;
-        }
-        if (lower === 'history') {
-            this.showHistory();
-            return true;
-        }
-        if (lower === 'plugins') {
-            this.showPluginPanel();
-            return true;
-        }
-        if (lower === 'mcp') {
-            this.showMcpAddress();
-            return true;
-        }
-        // 调试:通过MCP evaluate("__open plugin_id")打开全屏插件
-        if (lower.startsWith('__open ')) {
-            var pid = lower.substring(7).trim();
-            var pl = this.plugins.find(function(p) { return p.id === pid || p.command === pid; });
-            if (pl) this.openPlugin(pl);
-            else this.addOutput('插件未找到: ' + pid, 'error');
-            return true;
-        }
-        if (lower === 'feedback') {
-            this.showFeedback();
-            return true;
-        }
-        if (lower.startsWith('clear ')) {
-            const varName = cmd.substring(6).trim();
-            this.clearVariable(varName);
-            return true;
-        }
-        
-        // 检查是否是插件命令
-        if (this.handlePluginCommand(lower)) {
-            return true;
-        }
-        
-        return false;
-    }
-
-    handlePluginCommand(cmd) {
-        // 检查是否是带参数的插件命令格式: command[arg1, arg2, ...]
-        const paramMatch = cmd.match(/^([a-z0-9_-]+)\[(.*)\]$/i);
-        
-        if (paramMatch) {
-            const commandName = paramMatch[1].toLowerCase();
-            const argsString = paramMatch[2];
-            
-            // 查找匹配的插件
-            const plugin = this.plugins.find(p => p.command && p.command.toLowerCase() === commandName);
-            
-            if (plugin) {
-                // 解析参数
-                const args = this.parsePluginArgs(argsString);
-                
-                // 验证参数数量
-                if (args.length !== plugin.inputs.length) {
-                    this.addOutput(`❌ 参数数量不匹配。${plugin.name} 需要 ${plugin.inputs.length} 个参数,但提供了 ${args.length} 个`, 'error');
-                    this.scrollToBottom();
-                    return true;
-                }
-                
-                // 执行插件(直接模式)
-                this.executePluginDirect(plugin, args);
-                return true;
-            }
-        }
-        
-        // 查找匹配的插件命令(无参数,打开对话框)
-        for (const plugin of this.plugins) {
-            if (plugin.command && plugin.command.toLowerCase() === cmd) {
-                this.addOutput(`🔌 打开插件: ${plugin.name}`, 'help');
-                this.scrollToBottom();
-                this.openPlugin(plugin);
-                return true;
-            }
-        }
-        return false;
-    }
-    
-    parsePluginArgs(argsString) {
-        // 解析逗号分隔的参数,支持引号、大括号、方括号内的逗号
-        const args = [];
-        let current = '';
-        let inQuotes = false;
-        let quoteChar = '';
-        let bracketDepth = 0; // 跟踪 {} 的嵌套深度
-        let squareBracketDepth = 0; // 跟踪 [] 的嵌套深度
-        
-        for (let i = 0; i < argsString.length; i++) {
-            const char = argsString[i];
-            
-            // 处理引号
-            if ((char === '"' || char === "'") && (i === 0 || argsString[i-1] !== '\\')) {
-                if (!inQuotes) {
-                    inQuotes = true;
-                    quoteChar = char;
-                } else if (char === quoteChar) {
-                    inQuotes = false;
-                    quoteChar = '';
-                }
-                current += char;
-            }
-            // 处理大括号
-            else if (char === '{' && !inQuotes) {
-                bracketDepth++;
-                current += char;
-            }
-            else if (char === '}' && !inQuotes) {
-                bracketDepth--;
-                current += char;
-            }
-            // 处理方括号
-            else if (char === '[' && !inQuotes) {
-                squareBracketDepth++;
-                current += char;
-            }
-            else if (char === ']' && !inQuotes) {
-                squareBracketDepth--;
-                current += char;
-            }
-            // 处理逗号分隔符
-            else if (char === ',' && !inQuotes && bracketDepth === 0 && squareBracketDepth === 0) {
-                args.push(current.trim().replace(/^["']|["']$/g, ''));
-                current = '';
-            }
-            else {
-                current += char;
-            }
-        }
-        
-        if (current.trim()) {
-            args.push(current.trim().replace(/^["']|["']$/g, ''));
-        }
-        
-        return args;
-    }
-    
-    executePluginDirect(plugin, args) {
-        try {
-            console.log('executePluginDirect called with args:', args);
-            
-            // 构建输入对象,并转换为 Symja 可运算类型
-            const inputValues = {};
-            plugin.inputs.forEach((input, index) => {
-                const originalArg = args[index];
-                const convertedArg = this.convertInputToSymjaType(originalArg);
-                console.log(`Converting arg[${index}]: "${originalArg}" -> "${convertedArg}"`);
-                inputValues[input.name] = convertedArg;
-            });
-            
-            console.log('Final inputValues:', inputValues);
-            
-            // 用于存储插件的控制台输出
-            const pluginOutputs = [];
-
-            const ijsAPI = this.createIJSAPI(pluginOutputs);
-            const func = new Function('inputs', 'IJS', plugin.jsCode + '\nreturn calculate(inputs, IJS);');
-            const result = func(inputValues, ijsAPI);
-            
-            console.log('Plugin result:', result);
-            
-            // 验证返回值
-            if (typeof result !== 'string' || result.trim() === '') {
-                throw new Error('插件必须返回非空字符串类型的 Symja 表达式');
-            }
-            
-            // 验证返回值是否是有效的 Symja 表达式
-            try {
-                Android.symjaEval(result);
-            } catch (e) {
-                throw new Error('插件返回值必须是有效的 Symja 表达式,当前返回: ' + result);
-            }
-            
-            // 显示插件名称和参数
-            const argsDisplay = args.map((arg, i) => `${plugin.inputs[i].name}=${arg}`).join(', ');
-            this.addOutput(`${plugin.icon} ${plugin.name}[${argsDisplay}]`, 'input', { count: this.executionCount });
-            
-            // 显示插件的控制台输出(如果有)
-            if (pluginOutputs.length > 0) {
-                pluginOutputs.forEach(output => {
-                    this.addOutput(output, 'plugin-info');
-                });
-            }
-            
-            // 显示插件的返回值(可运算结果)
-            this.addOutput(result, 'result', { count: this.executionCount });
-            
-            // 将插件结果保存到 Symja 引擎,使其可以通过 % 引用
-            try {
-                Android.evaluate(`Out = ${result}`);
-            } catch (e) {
-                console.log('Plugin result storage error:', e);
-            }
-            
-            // 增加计数器
-            this.executionCount++;
-            this.saveExecutionCount();
-            this.updatePrompt();
-            
-            this.scrollToBottom();
-        } catch (e) {
-            console.log('Plugin execution error:', e);
-            this.addOutput(`插件执行错误: ${e.message}`, 'error');
-            this.scrollToBottom();
-        }
-    }
-    
-    // 将用户输入转换为 Symja 可运算的类型
-    convertInputToSymjaType(value) {
-        console.log(`convertInputToSymjaType called with: "${value}"`);
-        
-        if (!value || typeof value !== 'string') {
-            console.log('Value is not a string, returning as-is');
-            return value;
-        }
-        
-        const trimmed = value.trim();
-        
-        // 空值直接返回
-        if (trimmed === '') {
-            console.log('Value is empty, returning as-is');
-            return trimmed;
-        }
-        
-        // 检查是否是单个变量名(字母开头,后跟字母数字下划线)或 % 符号
-        const varNameMatch = trimmed.match(/^([a-zA-Z]\w*|%)$/);
-        console.log(`Variable name match result:`, varNameMatch);
-        
-        if (varNameMatch) {
-            // 直接尝试求值,如果是已定义的变量会返回值,否则返回变量名本身
-            try {
-                const evalResult = Android.symjaEval(trimmed);
-                console.log(`Evaluated "${trimmed}" to: "${evalResult}"`);
-                
-                // 如果求值结果与输入不同,说明变量已定义
-                if (evalResult !== trimmed) {
-                    console.log(`Variable ${trimmed} is defined, using value: ${evalResult}`);
-                    return evalResult;
-                } else {
-                    console.log(`Variable ${trimmed} is not defined or equals itself`);
-                }
-            } catch (e) {
-                console.error(`Failed to evaluate ${trimmed}:`, e);
-            }
-        }
-        
-        // 尝试通过 Symja 验证,如果能成功解析说明是有效的表达式
-        console.log(`Trying to validate as Symja expression: "${trimmed}"`);
-        try {
-            Android.symjaEval(trimmed);
-            console.log(`Valid Symja expression, returning: "${trimmed}"`);
-            return trimmed;
-        } catch (e) {
-            // 如果无法解析为 Symja 表达式,返回原值
-            // 插件内部可以根据需要进一步处理
-            console.log(`Not a valid Symja expression, returning as literal: "${trimmed}"`);
-            return trimmed;
-        }
-    }
-    
-    createIJSAPI(pluginOutputs) {
-        const self = this;
-        // 闭包引用 — IJS.input.xy() 需要访问 canvas 对象
-        const canvasRef = { _canvas: null, _logicalW: 600, _logicalH: 800 };
-        return {
-            // ── IJS.Symja 数学引擎 ─────────────────────────
-            Symja: {
-                print: function(message) {
-                    pluginOutputs.push(String(message));
-                },
-                eval: function(expression) {
-                    try { return Android.symjaEval(expression); }
-                    catch (e) { return '错误: 无法调用 Symja'; }
-                },
-                differentiate: function(expr, variable) { return this.eval(`D[${expr}, ${variable}]`); },
-                integrate: function(expr, variable) { return this.eval(`Integrate[${expr}, ${variable}]`); },
-                limit: function(expr, variable, point, direction) {
-                    try { return Android.limit(expr, variable, point, direction || ''); } catch (e) { return '错误:' + e.message; }
-                },
-                series: function(expr, variable, point, order) {
-                    try { return Android.series(expr, variable, point, order || 5); } catch (e) { return '错误:' + e.message; }
-                },
-                solve: function(equation, variable) { return this.eval(`Solve[${equation}, ${variable}]`); },
-                simplify: function(expr) { return this.eval(`Simplify[${expr}]`); },
-                expand: function(expr) { return this.eval(`Expand[${expr}]`); },
-                factor: function(expr) { return this.eval(`Factor[${expr}]`); },
-                apart: function(expr, variable) { try { return Android.apart(expr, variable); } catch (e) { return '错误:' + e.message; } },
-                together: function(expr) { try { return Android.together(expr); } catch (e) { return '错误:' + e.message; } },
-                trigSimplify: function(expr) { try { return Android.trigSimplify(expr); } catch (e) { return '错误:' + e.message; } },
-                sum: function(expr, variable, start, end) {
-                    try { return Android.sum(expr, variable, String(start), String(end)); } catch (e) { return '错误:' + e.message; }
-                },
-                product: function(expr, variable, start, end) {
-                    try { return Android.product(expr, variable, String(start), String(end)); } catch (e) { return '错误:' + e.message; }
-                },
-                matrix: {
-                    det: function(matrix) { try { return Android.matrixOp('Det', matrix); } catch (e) { return '错误:' + e.message; } },
-                    inverse: function(matrix) { try { return Android.matrixOp('Inverse', matrix); } catch (e) { return '错误:' + e.message; } },
-                    transpose: function(matrix) { try { return Android.matrixOp('Transpose', matrix); } catch (e) { return '错误:' + e.message; } },
-                    eigenvalues: function(matrix) { try { return Android.matrixOp('Eigenvalues', matrix); } catch (e) { return '错误:' + e.message; } },
-                    rank: function(matrix) { try { return Android.matrixOp('MatrixRank', matrix); } catch (e) { return '错误:' + e.message; } }
-                },
-                gcd: function(a, b) { try { return Android.gcd(String(a), String(b)); } catch (e) { return '错误:' + e.message; } },
-                lcm: function(a, b) { try { return Android.lcm(String(a), String(b)); } catch (e) { return '错误:' + e.message; } },
-                factorInteger: function(n) { try { return Android.factorInteger(String(n)); } catch (e) { return '错误:' + e.message; } },
-                isPrime: function(n) { try { return Android.isPrime(String(n)); } catch (e) { return '错误:' + e.message; } },
-                fibonacci: function(n) { try { return Android.fibonacci(n); } catch (e) { return '错误:' + e.message; } },
-                binomial: function(n, k) { try { return Android.binomial(String(n), String(k)); } catch (e) { return '错误:' + e.message; } },
-                numeric: function(expr, precision) {
-                    try { return Android.numericEval(expr, precision || 16); } catch (e) { return '错误:' + e.message; }
-                },
-                plot: function(func, variable, xMin, xMax) {
-                    try { return Android.evaluatePlot('Plot[' + func + ', {' + variable + ', ' + xMin + ', ' + xMax + '}]'); } catch (e) { return 'Error:' + e.message; }
-                },
-                parametricPlot: function(xf, yf, t, tMin, tMax) {
-                    try { return Android.evaluatePlot('ParametricPlot[{' + xf + ', ' + yf + '}, {' + t + ', ' + tMin + ', ' + tMax + '}]'); } catch (e) { return 'Error:' + e.message; }
-                },
-                listPlot: function(points) {
-                    try { return Android.evaluatePlot('ListPlot[' + points + ']'); } catch (e) { return 'Error:' + e.message; }
-                },
-                toTeX: function(expr) { try { return Android.toTeX(expr); } catch (e) { return '错误:' + e.message; } },
-                renderLatex: function(latex) { symjaConsole.addOutput(latex, 'result', { renderLatex: true }); return '已渲染 LaTeX'; }
-            },
-
-            // ── IJS.canvas 绘图 ────────────────────────────
-            canvas: {
-                _ctx: null,
-                _canvas: null,
-                _frameId: null,
-                create: function(w, h) {
-                    const panel = document.getElementById('fullscreenContent');
-                    if (!panel) return null;
-                    panel.innerHTML = '';
-                    const c = document.createElement('canvas');
-                    const dpr = window.devicePixelRatio || 1;
-                    // 自适应:未指定尺寸则填满全屏面板
-                    const cw = w || panel.clientWidth || window.innerWidth;
-                    const ch = h || panel.clientHeight || window.innerHeight;
-                    c.width = cw * dpr;
-                    c.height = ch * dpr;
-                    c.style.width = cw + 'px';
-                    c.style.height = ch + 'px';
-                    c.style.maxWidth = '100%';
-                    c.style.maxHeight = '100%';
-                    this._canvas = c;
-                    this._ctx = c.getContext('2d');
-                    this._ctx.scale(dpr, dpr);
-                    this._logicalW = cw;
-                    this._logicalH = ch;
-                    // 同步给 input.xy() 的闭包引用
-                    canvasRef._canvas = c;
-                    canvasRef._logicalW = cw;
-                    canvasRef._logicalH = ch;
-                    panel.appendChild(c);
-                    return this._ctx;
-                },
-                /** 获取逻辑画布宽度(供插件做自适应布局) */
-                width: function() { return this._logicalW || 600; },
-                /** 获取逻辑画布高度 */
-                height: function() { return this._logicalH || 800; },
-                getContext: function() { return this._ctx; },
-                getSize: function() {
-                    const dpr = window.devicePixelRatio || 1;
-                    return this._canvas ? { width: this._canvas.width / dpr, height: this._canvas.height / dpr } : { width: 0, height: 0 };
-                },
-                clear: function() {
-                    if (this._ctx && this._canvas) {
-                        const dpr = window.devicePixelRatio || 1;
-                        this._ctx.clearRect(0, 0, this._canvas.width / dpr, this._canvas.height / dpr);
-                    }
-                },
-                fill: function(color) {
-                    if (this._ctx && this._canvas) {
-                        const dpr = window.devicePixelRatio || 1;
-                        this._ctx.fillStyle = color || '#FFFFFF';
-                        this._ctx.fillRect(0, 0, this._canvas.width / dpr, this._canvas.height / dpr);
-                    }
-                },
-                size: function(w, h) {
-                    if (this._canvas) {
-                        this._canvas.width = w;
-                        this._canvas.height = h;
-                        this._logicalW = w;
-                        this._logicalH = h;
-                        canvasRef._logicalW = w;
-                        canvasRef._logicalH = h;
-                    }
-                },
-                // 动画循环
-                loop: function(callback) {
-                    const selfCanvas = this;
-                    function frame() {
-                        selfCanvas._frameId = requestAnimationFrame(frame);
-                        callback();
-                    }
-                    this.stop();
-                    selfCanvas._frameId = requestAnimationFrame(frame);
-                },
-                stop: function() {
-                    if (this._frameId) {
-                        cancelAnimationFrame(this._frameId);
-                        this._frameId = null;
-                    }
-                }
-            },
-
-            // ── IJS.input 触摸交互 ──────────────────────────
-            input: {
-                _callbacks: {},
-                onTouchStart: function(cb) { this._callbacks.touchstart = cb; },
-                onTouchMove: function(cb) { this._callbacks.touchmove = cb; },
-                onTouchEnd: function(cb) { this._callbacks.touchend = cb; this._bind(); },
-                /** 模拟触摸(MCP 调试用) */
-                _fireTouch: function(x, y) {
-                    if (this._callbacks.touchend) {
-                        this._callbacks.touchend({
-                            changedTouches: [{clientX: x, clientY: y}],
-                            preventDefault: function(){}
-                        });
-                    }
-                },
-                /** 将触摸事件的 clientX/Y 转换为画布逻辑坐标 */
-                xy: function(e) {
-                    var touch = e.changedTouches ? e.changedTouches[0] : (e.touches ? e.touches[0] : e);
-                    if (!touch) return {x:0,y:0};
-                    var c = canvasRef._canvas;
-                    if (!c) return {x: touch.clientX, y: touch.clientY};
-                    var r = c.getBoundingClientRect();
-                    return {
-                        x: (touch.clientX - r.left) * (canvasRef._logicalW / r.width),
-                        y: (touch.clientY - r.top) * (canvasRef._logicalH / r.height)
-                    };
-                },
-                _bound: false,
-                _bind: function() {
-                    if (this._bound) return;
-                    this._bound = true;
-                    const panel = document.getElementById('fullscreenContent');
-                    if (!panel) return;
-                    const selfInput = this;
-                    panel.addEventListener('touchstart', function(e) {
-                        e.preventDefault();
-                        if (selfInput._callbacks.touchstart) selfInput._callbacks.touchstart(e);
-                    }, { passive: false });
-                    panel.addEventListener('touchmove', function(e) {
-                        e.preventDefault();
-                        if (selfInput._callbacks.touchmove) selfInput._callbacks.touchmove(e);
-                    }, { passive: false });
-                    panel.addEventListener('touchend', function(e) {
-                        if (selfInput._callbacks.touchend) selfInput._callbacks.touchend(e);
-                    });
-                }
-            },
-
-            // ── IJS.io 文件读写 (应用数据目录) ────────────────
-            io: {
-                read: function(filename) {
-                    try { return Android.ioRead(filename); } catch (e) { return null; }
-                },
-                write: function(filename, content) {
-                    try { Android.ioWrite(filename, content); return true; } catch (e) { return false; }
-                },
-                list: function() {
-                    try { return JSON.parse(Android.ioList()); } catch (e) { return []; }
-                },
-                delete: function(filename) {
-                    try { Android.ioDelete(filename); return true; } catch (e) { return false; }
-                }
-            },
-
-            // ── IJS.audio 音效 ──────────────────────────────
-            audio: {
-                _ctx: null,
-                _getCtx: function() {
-                    if (!this._ctx) this._ctx = new (window.AudioContext || window.webkitAudioContext)();
-                    return this._ctx;
-                },
-                beep: function(freq, duration, type) {
-                    try {
-                        const ctx = this._getCtx();
-                        const osc = ctx.createOscillator();
-                        const gain = ctx.createGain();
-                        osc.type = type || 'square';
-                        osc.frequency.value = freq || 440;
-                        gain.gain.value = 0.15;
-                        gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + (duration || 0.2));
-                        osc.connect(gain);
-                        gain.connect(ctx.destination);
-                        osc.start(ctx.currentTime);
-                        osc.stop(ctx.currentTime + (duration || 0.2));
-                    } catch(e) {}
-                },
-                playNote: function(note, octave, duration) {
-                    const notes = { C: 262, D: 294, E: 330, F: 349, G: 392, A: 440, B: 494 };
-                    const freq = (notes[note] || 440) * Math.pow(2, (octave || 4) - 4);
-                    this.beep(freq, duration || 0.2, 'square');
-                },
-                playMelody: function(melody, tempo) {
-                    let t = 0;
-                    const beat = (tempo || 120) / 60;
-                    const selfAudio = this;
-                    melody.forEach(function(m) {
-                        if (m.note === 'R') { t += m.dur / beat; return; }
-                        setTimeout(function() {
-                            selfAudio.playNote(m.note, m.octave || 4, m.dur / beat * 0.9);
-                        }, t * 1000);
-                        t += m.dur / beat;
-                    });
-                },
-                playFile: function(filename) {
-                    // 暂不支持,留待扩展
-                }
-            },
-
-            // ── IJS.ui 面板控制 ─────────────────────────────
-            ui: {
-                close: function() {
-                    document.getElementById('fullscreenPanel').classList.remove('show');
-                },
-                setBackground: function(color) {
-                    const panel = document.getElementById('fullscreenContent');
-                    if (panel) panel.style.background = color;
-                }
-            }
-        };
-    }
-
-    trackVariables(expression) {
-        // 简单的变量赋值检测
-        const match = expression.match(/^([a-zA-Z]\w*)\s*=/);
-        if (match) {
-            this.variables.add(match[1]);
-        }
-    }
-
-    clearVariable(varName) {
-        try {
-            const result = Android.clearVariable(varName);
-            this.addOutput(result, 'help');
-            this.variables.delete(varName);
-        } catch (e) {
-            this.addOutput(`❌ 清除失败: ${e.message}`, 'error');
-        }
-        this.scrollToBottom();
-    }
-
-    showHistory() {
-        if (this.history.length === 0) {
-            this.addOutput('📜 历史记录为空', 'help');
-        } else {
-            const recent = this.history.slice(-10).reverse();
-            const historyText = recent.map((cmd, i) => `${recent.length - i}. ${cmd}`).join('\n');
-            this.addOutput(`📜 最近的命令:\n${historyText}`, 'help');
-        }
-        this.scrollToBottom();
-    }
-
-    showMcpAddress() {
-        try {
-            const running = typeof Android !== 'undefined' && Android.isMcpRunning && Android.isMcpRunning();
-            const port = '8734';
-            const status = running ? '✅ 运行中' : '⭕ 未启动';
-            const rikkaConfig = JSON.stringify({
-                mcpServers: {
-                    ijs: {
-                        type: "streamable-http",
-                        url: `http://127.0.0.1:${port}/mcp`,
-                        commonOptions: {
-                            name: "IJS - Symja 符号计算",
-                            enable: true,
-                            headers: []
-                        }
-                    }
-                }
-            }, null, 2);
-            this.addOutput(`🌐 MCP 服务器 ${status}\n\n📍 地址: http://127.0.0.1:${port}/mcp\n\n📋 RikkaHub 配置:\n${rikkaConfig}\n\n💻 Claude Desktop 配置:\n{"mcpServers":{"ijs":{"url":"http://127.0.0.1:${port}/mcp"}}}\n\n🔧 adb 转发: adb forward tcp:${port} tcp:${port}`, 'help');
-        } catch(e) {
-            this.addOutput('🌐 MCP 服务器\n   地址: http://127.0.0.1:8734/mcp', 'help');
-        }
-        this.scrollToBottom();
-    }
-
-    showFeedback() {
-        const email = 'nonvioce@qq.com';
-        const feedbackId = 'feedback-' + Date.now();
-        
-        // 创建带复制按钮的反馈信息
-        const line = document.createElement('div');
-        line.className = 'terminal-line';
-        line.innerHTML = `
-            <div class="feedback-line">
-                <div class="feedback-icon">📧</div>
-                <div class="feedback-content">
-                    <div class="feedback-title">反馈与建议</div>
-                    <div class="feedback-text">
-                        欢迎通过邮件向我们反馈问题或提出建议!
-                    </div>
-                    <div class="feedback-email-container">
-                        <span class="feedback-email">${email}</span>
-                        <button class="feedback-copy-btn" onclick="symjaConsole.copyFeedbackEmail('${email}', this)">
-                            📋 复制邮箱
-                        </button>
-                    </div>
-                </div>
-            </div>
-        `;
-        
-        this.output.appendChild(line);
-        this.scrollToBottom();
-    }
-
-    copyFeedbackEmail(email, button) {
-        // 创建临时文本区域
-        const textarea = document.createElement('textarea');
-        textarea.value = email;
-        textarea.style.position = 'fixed';
-        textarea.style.opacity = '0';
-        document.body.appendChild(textarea);
-        textarea.select();
-        
-        try {
-            document.execCommand('copy');
-            button.textContent = '✅ 已复制';
-            button.style.background = '#4CAF50';
-            setTimeout(() => {
-                button.textContent = '📋 复制邮箱';
-                button.style.background = '';
-            }, 2000);
-        } catch (e) {
-            console.error('Copy failed:', e);
-            button.textContent = '❌ 复制失败';
-            setTimeout(() => {
-                button.textContent = '📋 复制邮箱';
-            }, 2000);
-        }
-        
-        document.body.removeChild(textarea);
-    }
-
-    addToHistory(cmd) {
-        // 避免重复的连续命令
-        if (this.history.length === 0 || this.history[this.history.length - 1] !== cmd) {
-            this.history.push(cmd);
-            // 限制历史记录大小
-            if (this.history.length > 100) {
-                this.history.shift();
-            }
-            this.saveHistory();
-        }
-        this.historyIndex = this.history.length;
-    }
-
-    loadHistory() {
-        try {
-            if (typeof Android !== 'undefined' && Android.loadHistory) {
-                const saved = Android.loadHistory();
-                return saved ? JSON.parse(saved) : [];
-            }
-        } catch (e) {}
-        // 回退到 localStorage
-        try {
-            const saved = localStorage.getItem('symja_history');
-            return saved ? JSON.parse(saved) : [];
-        } catch (e) { return []; }
-    }
-
-    saveHistory() {
-        const json = JSON.stringify(this.history);
-        try {
-            if (typeof Android !== 'undefined' && Android.saveHistory) {
-                Android.saveHistory(json);
-            }
-        } catch (e) {}
-        try {
-            localStorage.setItem('symja_history', json);
-        } catch (e) {}
-    }
-
-    saveSession(name) {
-        try {
-            const session = {
-                history: this.history,
-                variables: Array.from(this.variables),
-                timestamp: new Date().toISOString()
-            };
-            localStorage.setItem(`symja_session_${name}`, JSON.stringify(session));
-            this.addOutput(`💾 会话已保存: ${name}`, 'help');
-        } catch (e) {
-            this.addOutput(`❌ 保存失败: ${e.message}`, 'error');
-        }
-        this.scrollToBottom();
-    }
-
-    addOutput(text, type, options = {}) {
-        const line = document.createElement('div');
-        line.className = 'terminal-line';
-
-        if (type === 'input') {
-            // 命令输入显示(带计数器)
-            const count = options.count !== undefined ? options.count : '';
-            line.innerHTML = `
-                <div class="command-line">
-                    <span class="command-prompt">[${count}]:</span>
-                    <span class="command-text">${this.escapeHtml(text)}</span>
-                </div>
-            `;
-        } else if (type === 'result') {
-            const isError = text.startsWith('错误:');
-            
-            // 检查是否需要 LaTeX 渲染
-            if (options.renderLatex && !isError) {
-                const count = options.count !== undefined ? options.count : '';
-                line.innerHTML = `
-                    <div class="latex-line">
-                        <div class="result-prompt">[${count}]:</div>
-                        <div class="latex-content">
-                            <div class="latex-rendered" id="latex-${Date.now()}"></div>
-                            <div class="latex-source" style="display: none;">${this.escapeHtml(text)}</div>
-                            <div class="latex-actions">
-                                <button class="latex-btn latex-toggle" onclick="symjaConsole.toggleLatexSource(this)">显示源码</button>
-                                <button class="latex-btn latex-copy" onclick="symjaConsole.copyLatex(this)">复制 LaTeX</button>
-                            </div>
-                        </div>
-                    </div>
-                `;
-                this.output.appendChild(line);
-                
-                // 渲染 LaTeX
-                const renderedDiv = line.querySelector('.latex-rendered');
-                this.renderLatex(text, renderedDiv);
-            } else {
-                const className = isError ? 'error-line' : 'result-line';
-                const count = options.count !== undefined ? options.count : '';
-                line.innerHTML = `
-                    <div class="${className}">
-                        <span class="result-prompt">[${count}]:</span>
-                        <span class="result-text">${this.escapeHtml(text)}</span>
-                    </div>
-                `;
-            }
-        } else if (type === 'help') {
-            line.innerHTML = `<div class="info-line">${this.escapeHtml(text)}</div>`;
-        } else if (type === 'error') {
-            line.innerHTML = `<div class="error-line">${this.escapeHtml(text)}</div>`;
-        } else if (type === 'plugin-info') {
-            // 插件的控制台输出(通过 Symja.print() 输出的内容)
-            line.innerHTML = `<div class="plugin-info-line">${this.escapeHtml(text)}</div>`;
-        } else if (type === 'plugin') {
-            // 插件的返回值(可运算结果)
-            line.innerHTML = `<div class="plugin-line">${this.escapeHtml(text)}</div>`;
-        } else if (type === 'mcp-input') {
-            const seq = options.seq || '';
-            line.innerHTML = `<div class="mcp-input-line">
-                <span class="mcp-badge">🌐 MCP</span>
-                <span class="mcp-prompt">[${seq}]:</span>
-                <span class="mcp-command-text">${this.escapeHtml(text)}</span>
-            </div>`;
-        } else if (type === 'mcp-latex') {
-            line.innerHTML = `<div class="latex-line">
-                <div class="latex-content">
-                    <div class="latex-rendered" id="latex-mcp-${Date.now()}"></div>
-                    <div class="latex-source" style="display:none">${this.escapeHtml(text)}</div>
-                    <div class="latex-actions">
-                        <button class="latex-btn latex-toggle" onclick="symjaConsole.toggleLatexSource(this)">显示源码</button>
-                        <button class="latex-btn latex-copy" onclick="symjaConsole.copyLatex(this)">复制 LaTeX</button>
-                    </div>
-                </div>
-            </div>`;
-            this.output.appendChild(line);
-            const renderedDiv = line.querySelector('.latex-rendered');
-            this.renderLatex(text, renderedDiv);
-        } else if (type === 'mcp-result') {
-            line.innerHTML = `<div class="mcp-result-line">
-                <span class="mcp-prompt"></span>
-                <span class="mcp-result-text">${this.escapeHtml(text)}</span>
-            </div>`;
-        }
-        
-        this.output.appendChild(line);
-    }
-
-    // LaTeX 渲染功能
-    renderLatex(latex, element) {
-        try {
-            // 等待 KaTeX 加载
-            if (typeof katex !== 'undefined') {
-                // 清理 LaTeX 字符串
-                let cleanLatex = latex.trim();
-                
-                // 如果是 Symja 的 TeXForm 输出,可能需要一些转换
-                if (cleanLatex.startsWith('TeXForm:')) {
-                    cleanLatex = cleanLatex.substring(8).trim();
-                }
-                
-                // 使用 KaTeX 渲染
-                katex.render(cleanLatex, element, {
-                    throwOnError: false,
-                    displayMode: true,
-                    output: 'html',
-                    trust: false,
-                    strict: false
-                });
-            } else {
-                // KaTeX 未加载,显示格式化的源码
-                element.style.fontFamily = 'var(--font-mono)';
-                element.style.fontSize = '14px';
-                element.style.padding = '12px';
-                element.style.background = '#F5F5F5';
-                element.style.borderRadius = '4px';
-                element.style.overflowX = 'auto';
-                element.textContent = latex;
-                console.warn('KaTeX not loaded, showing source code');
-            }
-        } catch (e) {
-            // 渲染失败,显示源码
-            element.style.fontFamily = 'var(--font-mono)';
-            element.style.fontSize = '14px';
-            element.style.padding = '12px';
-            element.style.background = '#F5F5F5';
-            element.style.borderRadius = '4px';
-            element.style.overflowX = 'auto';
-            element.textContent = latex;
-            console.error('LaTeX rendering error:', e);
-        }
-    }
-
-    // 切换显示 LaTeX 源码
-    toggleLatexSource(button) {
-        const parent = button.closest('.latex-line');
-        if (!parent) return;
-        
-        const source = parent.querySelector('.latex-source');
-        if (!source) return;
-        
-        if (source.style.display === 'none') {
-            source.style.display = 'block';
-            button.textContent = '隐藏源码';
-        } else {
-            source.style.display = 'none';
-            button.textContent = '显示源码';
-        }
-    }
-
-    // 复制 LaTeX 代码
-    copyLatex(button) {
-        const parent = button.closest('.latex-line');
-        if (!parent) return;
-        
-        const source = parent.querySelector('.latex-source');
-        if (!source) return;
-        
-        const text = source.textContent;
-        
-        // 创建临时文本区域
-        const textarea = document.createElement('textarea');
-        textarea.value = text;
-        textarea.style.position = 'fixed';
-        textarea.style.opacity = '0';
-        document.body.appendChild(textarea);
-        textarea.select();
-        
-        try {
-            document.execCommand('copy');
-            button.textContent = '已复制!';
-            setTimeout(() => {
-                button.textContent = '复制 LaTeX';
-            }, 2000);
-        } catch (e) {
-            console.error('Copy failed:', e);
-        }
-        
-        document.body.removeChild(textarea);
-    }
-
-    showHelp() {
-        const helpPage = document.getElementById('helpPage');
-        const helpContent = document.getElementById('helpContent');
-        
-        if (!helpPage || !helpContent) return;
-        
-        // 隐藏键盘
-        this.hideKeyboard();
-        
-        helpContent.innerHTML = `
-            <table class="help-table">
-                <thead>
-                    <tr>
-                        <th>说明</th>
-                        <th>命令</th>
-                    </tr>
-                </thead>
-                <tbody>
-                    <tr><td>求导</td><td>D[x^2, x]</td></tr>
-                    <tr><td>复合函数求导</td><td>D[Sin[x]*Cos[x], x]</td></tr>
-                    <tr><td>不定积分</td><td>Integrate[x^2, x]</td></tr>
-                    <tr><td>定积分</td><td>Integrate[x^2, {x, 0, 1}]</td></tr>
-                    <tr><td>求极限</td><td>Limit[Sin[x]/x, x -> 0]</td></tr>
-                    <tr><td>泰勒展开</td><td>Series[Exp[x], {x, 0, 5}]</td></tr>
-                    <tr><td>求和</td><td>Sum[i^2, {i, 1, 10}]</td></tr>
-                    <tr><td>求积(阶乘)</td><td>Product[i, {i, 1, 5}]</td></tr>
-                    
-                    <tr><td>展开</td><td>Expand[(x+1)^3]</td></tr>
-                    <tr><td>因式分解</td><td>Factor[x^2-4]</td></tr>
-                    <tr><td>化简</td><td>Simplify[Sin[x]^2+Cos[x]^2]</td></tr>
-                    <tr><td>通分</td><td>Together[(1/x)+(1/y)]</td></tr>
-                    <tr><td>部分分式</td><td>Apart[(x+1)/(x^2-1), x]</td></tr>
-                    <tr><td>约分</td><td>Cancel[(x^2-1)/(x-1)]</td></tr>
-                    <tr><td>合并同类项</td><td>Collect[x^2+3*x+2*x^2, x]</td></tr>
-                    
-                    <tr><td>解方程</td><td>Solve[x^2-5*x+6==0, x]</td></tr>
-                    <tr><td>方程组</td><td>Solve[{x+y==1, x-y==3}, {x,y}]</td></tr>
-                    <tr><td>数值求解</td><td>NSolve[x^3-2*x+1==0, x]</td></tr>
-                    <tr><td>求根</td><td>Roots[x^2-4==0, x]</td></tr>
-                    
-                    <tr><td>行列式</td><td>Det[{{1,2},{3,4}}]</td></tr>
-                    <tr><td>逆矩阵</td><td>Inverse[{{1,2},{3,4}}]</td></tr>
-                    <tr><td>转置</td><td>Transpose[{{1,2},{3,4}}]</td></tr>
-                    <tr><td>特征值</td><td>Eigenvalues[{{1,2},{3,4}}]</td></tr>
-                    <tr><td>特征向量</td><td>Eigenvectors[{{1,2},{3,4}}]</td></tr>
-                    <tr><td>矩阵秩</td><td>MatrixRank[{{1,2},{2,4}}]</td></tr>
-                    
-                    <tr><td>最大公约数</td><td>GCD[12, 18]</td></tr>
-                    <tr><td>最小公倍数</td><td>LCM[12, 18]</td></tr>
-                    <tr><td>质因数分解</td><td>FactorInteger[60]</td></tr>
-                    <tr><td>判断素数</td><td>PrimeQ[17]</td></tr>
-                    <tr><td>第 n 个素数</td><td>Prime[10]</td></tr>
-                    <tr><td>斐波那契数</td><td>Fibonacci[10]</td></tr>
-                    <tr><td>组合数 C(n,k)</td><td>Binomial[5, 2]</td></tr>
-                    
-                    <tr><td>高精度计算(50位)</td><td>N[Pi, 50]</td></tr>
-                    <tr><td>数值化</td><td>N[Sqrt[2]]</td></tr>
-                    <tr><td>四舍五入</td><td>Round[3.7]</td></tr>
-                    <tr><td>向下取整</td><td>Floor[3.7]</td></tr>
-                    <tr><td>向上取整</td><td>Ceiling[3.2]</td></tr>
-                    
-                    <tr><td>三角函数</td><td>Sin[x], Cos[x], Tan[x]</td></tr>
-                    <tr><td>反三角函数</td><td>ArcSin[x], ArcCos[x], ArcTan[x]</td></tr>
-                    <tr><td>双曲函数</td><td>Sinh[x], Cosh[x], Tanh[x]</td></tr>
-                    <tr><td>三角展开</td><td>TrigExpand[Sin[2*x]]</td></tr>
-                    <tr><td>三角化简</td><td>TrigReduce[Sin[x]^2]</td></tr>
-                    
-                    <tr><td>实部</td><td>Re[3+4*I]</td></tr>
-                    <tr><td>虚部</td><td>Im[3+4*I]</td></tr>
-                    <tr><td>模</td><td>Abs[3+4*I]</td></tr>
-                    <tr><td>辐角</td><td>Arg[3+4*I]</td></tr>
-                    <tr><td>共轭</td><td>Conjugate[3+4*I]</td></tr>
-                    
-                    <tr><td>生成表格</td><td>Table[i^2, {i, 1, 10}]</td></tr>
-                    <tr><td>生成数列</td><td>Range[1, 10]</td></tr>
-                    <tr><td>带步长的数列</td><td>Range[0, 1, 0.1]</td></tr>
-                    <tr><td>列表长度</td><td>Length[{1,2,3,4}]</td></tr>
-                    <tr><td>第一个元素</td><td>First[{1,2,3}]</td></tr>
-                    <tr><td>最后一个元素</td><td>Last[{1,2,3}]</td></tr>
-                    <tr><td>取前 n 个</td><td>Take[{1,2,3,4,5}, 3]</td></tr>
-                    <tr><td>删除前 n 个</td><td>Drop[{1,2,3,4,5}, 2]</td></tr>
-                    <tr><td>连接列表</td><td>Join[{1,2}, {3,4}]</td></tr>
-                    <tr><td>反转列表</td><td>Reverse[{1,2,3}]</td></tr>
-                    <tr><td>排序</td><td>Sort[{3,1,4,1,5}]</td></tr>
-                    
-                    <tr><td>替换规则</td><td>b+c /. b->1</td></tr>
-                    <tr><td>变量替换</td><td>x^2+2*x+1 /. x->3</td></tr>
-                    <tr><td>多重替换</td><td>expr /. {a->1, b->2}</td></tr>
-                    
-                    <tr><td>微分方程</td><td>DSolve[y'[x]==y[x], y[x], x]</td></tr>
-                    <tr><td>递推方程</td><td>RSolve[a[n]==2*a[n-1]+1, a[n], n]</td></tr>
-                    
-                    <tr><td>拉普拉斯变换</td><td>LaplaceTransform[Sin[t], t, s]</td></tr>
-                    <tr><td>逆拉普拉斯变换</td><td>InverseLaplaceTransform[1/(s^2+1), s, t]</td></tr>
-                    <tr><td>傅里叶变换</td><td>FourierTransform[Exp[-x^2], x, w]</td></tr>
-                    
-                    <tr><td>求最大值</td><td>Maximize[x^2-4*x+3, x]</td></tr>
-                    <tr><td>求最小值</td><td>Minimize[x^2-4*x+3, x]</td></tr>
-                    
-                    <tr><td>平均值</td><td>Mean[{1,2,3,4,5}]</td></tr>
-                    <tr><td>中位数</td><td>Median[{1,2,3,4,5}]</td></tr>
-                    <tr><td>方差</td><td>Variance[{1,2,3,4,5}]</td></tr>
-                    <tr><td>标准差</td><td>StandardDeviation[{1,2,3,4,5}]</td></tr>
-                    
-                    <tr><td>LaTeX 格式</td><td>TeXForm[x^2 + 2*x + 1]</td></tr>
-                    <tr><td>完整形式</td><td>FullForm[x+y]</td></tr>
-                    <tr><td>输入形式</td><td>InputForm[expr]</td></tr>
-                    
-                    <tr><td>显示帮助</td><td>help 或 ?</td></tr>
-                    <tr><td>清空输出</td><td>clear 或 cls</td></tr>
-                    <tr><td>命令历史</td><td>history</td></tr>
-                    <tr><td>清除变量</td><td>clear &lt;变量&gt;</td></tr>
-                    <tr><td>插件管理</td><td>plugins</td></tr>
-                    <tr><td>显示反馈邮箱</td><td>feedback</td></tr>
-                    
-                    <tr><td>圆周率 π</td><td>Pi</td></tr>
-                    <tr><td>自然常数 e</td><td>E</td></tr>
-                    <tr><td>虚数单位 i</td><td>I</td></tr>
-                    <tr><td>无穷大 ∞</td><td>Infinity</td></tr>
-                    <tr><td>布尔值</td><td>True, False</td></tr>
-                    <tr><td>上一次的输出结果</td><td>%</td></tr>
-                </tbody>
-            </table>
-        `;
-        
-        helpPage.classList.add('show');
-    }
-    
-    hideHelp() {
-        const helpPage = document.getElementById('helpPage');
-        if (helpPage) {
-            helpPage.classList.remove('show');
-        }
-    }
-    
-    showSettings() {
-        const panel = document.getElementById('settingsPanel');
-        if (!panel) return;
-        this.hideKeyboard();
-        document.getElementById('settingMcpEnabled').value = this.getMcpEnabled() ? '1' : '0';
-        document.getElementById('settingMcpPort').value = this.getSetting('mcpPort', '8734');
-        document.getElementById('settingFontSize').value = this.getSetting('fontSize', '14');
-        panel.classList.add('show');
-    }
-
-    hideSettings() {
-        document.getElementById('settingsPanel')?.classList.remove('show');
-    }
-
-    applySettings() {
-        const wasEnabled = this.getMcpEnabled();
-        const enabled = document.getElementById('settingMcpEnabled').value === '1';
-        const port = document.getElementById('settingMcpPort').value;
-        const fontSize = document.getElementById('settingFontSize').value;
-        this.saveSetting('mcpPort', port);
-        this.saveSetting('fontSize', fontSize);
-        this.setMcpEnabled(enabled);
-        document.querySelector('.terminal-output').style.fontSize = fontSize + 'px';
-        document.querySelector('.terminal-input').style.fontSize = fontSize + 'px';
-        this.hideSettings();
-        this.addOutput('⚙️ 设置已应用', 'help');
-        this.scrollToBottom();
-
-        // MCP 状态变更时更新轮询
-        if (enabled !== wasEnabled) {
-            if (enabled) {
-                this.startMcpPollingIfNeeded();
-            } else {
-                this.stopMcpPolling();
-            }
-        }
-    }
-
-    getMcpEnabled() {
-        try {
-            if (typeof Android !== 'undefined' && Android.getMcpEnabled !== undefined) {
-                return Android.getMcpEnabled();
-            }
-        } catch(e) {}
-        return this.getSetting('mcpEnabled', '1') === '1';
-    }
-
-    setMcpEnabled(val) {
-        this.saveSetting('mcpEnabled', val ? '1' : '0');
-        try {
-            if (typeof Android !== 'undefined' && Android.setMcpEnabled) {
-                Android.setMcpEnabled(val);
-            }
-        } catch(e) {}
-    }
-
-    startMcpPollingIfNeeded() {
-        if (!this.getMcpEnabled()) return;
-        if (!this.mcpPollTimer) {
-            this.mcpPollTimer = setInterval(() => this.pollMcpEvents(), 1000);
-        }
-    }
-
-    stopMcpPolling() {
-        if (this.mcpPollTimer) { clearInterval(this.mcpPollTimer); this.mcpPollTimer = null; }
-    }
-
-    getSetting(key, def) {
-        try { return localStorage.getItem('ijs_' + key) || def; } catch(e) { return def; }
-    }
-
-    saveSetting(key, val) {
-        try { localStorage.setItem('ijs_' + key, val); } catch(e) {}
-    }
-
-    loadSettings() {
-        const fs = this.getSetting('fontSize', '14');
-        try {
-            document.querySelector('.terminal-output').style.fontSize = fs + 'px';
-            document.querySelector('.terminal-input').style.fontSize = fs + 'px';
-        } catch(e) {}
-    }
-
-    clear() {
-        this.output.innerHTML = '';
-        this.showWelcome();
-        // 重置计数器
-        this.executionCount = 1;
-        this.saveExecutionCount();
-        this.updatePrompt();
-    }
-
-    reset() {
-        try {
-            const result = Android.reset();
-            this.variables.clear();
-            this.addOutput('🔄 ' + result, 'help');
-            this.scrollToBottom();
-            // 重置计数器
-            this.executionCount = 1;
-            this.saveExecutionCount();
-            this.updatePrompt();
-        } catch (e) {
-            this.addOutput('错误: 无法重置计算器', 'error');
-        }
-    }
-
-    updatePrompt() {
-        if (this.inputPrompt) {
-            this.inputPrompt.textContent = `[${this.executionCount}]:`;
-        }
-    }
-
-    loadExecutionCount() {
-        return 1;  // 每次打开应用从1开始
-    }
-
-    saveExecutionCount() {
-        // 不持久化编号,每次打开都重置
-    }
-
-    navigateHistory(direction) {
-        if (this.history.length === 0) {
-            return;
-        }
-
-        // 保存当前输入
-        if (this.historyIndex === this.history.length) {
-            this.tempInput = this.input.value;
-        }
-
-        this.historyIndex += direction;
-
-        if (this.historyIndex < 0) {
-            this.historyIndex = 0;
-        } else if (this.historyIndex >= this.history.length) {
-            this.historyIndex = this.history.length;
-            this.input.value = this.tempInput;
-            return;
-        }
-
-        this.input.value = this.history[this.historyIndex];
-        // 光标移到末尾
-        setTimeout(() => {
-            this.input.setSelectionRange(this.input.value.length, this.input.value.length);
-        }, 0);
-    }
-
-    // 自动补全功能
-    updateCompletionList() {
-        const value = this.input.value;
-        const cursorPos = this.input.selectionStart;
-        const textBeforeCursor = value.substring(0, cursorPos);
-        const match = textBeforeCursor.match(/[A-Za-z0-9_-]+$/);
-        
-        console.log('updateCompletionList:', { value, textBeforeCursor, match });
-        
-        if (match && match[0].length >= 2) {
-            const prefix = match[0];
-            console.log('Searching completions for prefix:', prefix);
-            
-            // 特殊命令列表
-            const specialCommands = ['help', 'clear', 'cls', 'history', 'plugins', 'mcp', 'feedback'];
-            
-            // 获取插件命令列表
-            const pluginCommands = this.plugins
-                .filter(p => p.command && p.command.length > 0)
-                .map(p => p.command);
-            
-            try {
-                const completionsJson = Android.getCompletions(prefix);
-                console.log('Raw completions JSON:', completionsJson);
-                
-                // 解析补全结果 - 可能是逗号分隔的字符串或 JSON 数组
-                let symjaCompletions = [];
-                if (completionsJson.startsWith('[')) {
-                    // JSON 数组格式
-                    symjaCompletions = JSON.parse(completionsJson);
-                } else {
-                    // 逗号分隔的字符串格式
-                    symjaCompletions = completionsJson.split(',').map(s => s.trim()).filter(s => s.length > 0);
-                }
-                
-                // 添加匹配的特殊命令
-                const lowerPrefix = prefix.toLowerCase();
-                const matchingCommands = specialCommands.filter(cmd => cmd.startsWith(lowerPrefix));
-                
-                // 添加匹配的插件命令
-                const matchingPluginCommands = pluginCommands.filter(cmd => cmd.toLowerCase().startsWith(lowerPrefix));
-                
-                // 合并并去重(插件命令优先显示)
-                this.completions = [...new Set([...matchingPluginCommands, ...matchingCommands, ...symjaCompletions])];
-                
-                console.log('Parsed completions:', this.completions);
-                
-                if (this.completions.length > 0) {
-                    this.showCompletionList();
-                } else {
-                    this.hideCompletionList();
-                }
-            } catch (e) {
-                console.error('Completion error:', e);
-                this.completions = [];
-                this.hideCompletionList();
-            }
-        } else {
-            this.completions = [];
-            this.hideCompletionList();
-        }
-    }
-
-    showCompletionList() {
-        console.log('showCompletionList called');
-        console.log('completionList element:', this.completionList);
-        console.log('completions:', this.completions);
-        
-        if (!this.completionList) {
-            console.error('completionList element not found');
-            return;
-        }
-        
-        if (this.completions.length === 0) {
-            console.log('No completions to show');
-            return;
-        }
-        
-        this.completionList.innerHTML = '';
-        
-        // 最多显示 8 个补全项
-        const itemsToShow = this.completions.slice(0, 8);
-        console.log('Items to show:', itemsToShow);
-        
-        itemsToShow.forEach(completion => {
-            const item = document.createElement('div');
-            item.className = 'completion-item';
-            
-            // 检查是否是插件命令
-            const plugin = this.plugins.find(p => p.command === completion);
-            if (plugin) {
-                // 插件命令,显示图标和名称
-                item.innerHTML = `<span style="margin-right: 8px;">${plugin.icon}</span>${completion} <span style="color: #999; font-size: 12px; margin-left: 8px;">(${plugin.name})</span>`;
-            } else {
-                // 普通命令或函数
-                item.textContent = completion;
-            }
-            
-            item.addEventListener('click', () => {
-                this.applyCompletion(completion);
-            });
-            this.completionList.appendChild(item);
-        });
-        
-        this.completionList.classList.add('show');
-        console.log('Completion list should now be visible');
-    }
-
-    hideCompletionList() {
-        if (this.completionList) {
-            this.completionList.classList.remove('show');
-        }
-    }
-
-    applyCompletion(completion) {
-        const value = this.input.value;
-        const cursorPos = this.input.selectionStart;
-        const textBeforeCursor = value.substring(0, cursorPos);
-        const textAfterCursor = value.substring(cursorPos);
-
-        const match = textBeforeCursor.match(/[A-Za-z0-9_-]+$/);
-        if (match) {
-            const prefix = match[0];
-            const beforePrefix = textBeforeCursor.substring(0, textBeforeCursor.length - prefix.length);
-            
-            // 检查是否是插件命令
-            const plugin = this.plugins.find(p => p.command === completion);
-            if (plugin && plugin.inputs && plugin.inputs.length > 0) {
-                // 插件命令,添加参数括号和提示
-                const paramHints = plugin.inputs.map(input => input.name).join(', ');
-                this.input.value = beforePrefix + completion + '[' + paramHints + ']' + textAfterCursor;
-                
-                // 将光标放在第一个参数位置
-                const newCursorPos = beforePrefix.length + completion.length + 1;
-                this.input.setSelectionRange(newCursorPos, newCursorPos + plugin.inputs[0].name.length);
-            } else {
-                // 普通命令或函数
-                this.input.value = beforePrefix + completion + textAfterCursor;
-                
-                const newCursorPos = beforePrefix.length + completion.length;
-                this.input.setSelectionRange(newCursorPos, newCursorPos);
-            }
-        }
-        
-        this.hideCompletionList();
-        this.input.focus();
-    }
-
-    handleAutoComplete() {
-        // 保留这个方法以防其他地方调用,但不再使用
-        this.updateCompletionList();
-    }
-
-    // 插件相关功能
-    loadPlugins() {
-        try {
-            const pluginsJson = Android.getPlugins();
-            console.log('Loaded plugins JSON:', pluginsJson);
-            this.plugins = JSON.parse(pluginsJson);
-            console.log('Parsed plugins count:', this.plugins.length);
-            console.log('Plugins:', this.plugins);
-        } catch (e) {
-            console.error('Failed to load plugins:', e);
-            this.plugins = [];
-        }
-    }
-
-    showPluginPanel() {
-        this.loadPlugins();
-        const panel = document.getElementById('pluginPanel');
-        
-        if (!panel) return;
-        
-        // 隐藏键盘
-        this.hideKeyboard();
-        
-        // 确保默认显示插件列表标签
-        const tabs = panel.querySelectorAll('.plugin-tab');
-        const contents = panel.querySelectorAll('.tab-content');
-        
-        tabs.forEach(tab => {
-            if (tab.dataset.tab === 'list') {
-                tab.classList.add('active');
-            } else {
-                tab.classList.remove('active');
-            }
-        });
-        
-        contents.forEach(content => {
-            if (content.dataset.content === 'list') {
-                content.classList.add('active');
-            } else {
-                content.classList.remove('active');
-            }
-        });
-        
-        // 更新插件下拉列表(用于分享和删除)
-        this.updatePluginSelects();
-        
-        // 使用统一的刷新方法
-        this.refreshPluginList();
-        
-        panel.classList.add('show');
-    }
-
-    hidePluginPanel() {
-        const panel = document.getElementById('pluginPanel');
-        if (panel) panel.classList.remove('show');
-    }
-
-    openPlugin(plugin) {
-        this.currentPlugin = plugin;
-        this.hidePluginPanel();
-
-        // Fullscreen mode — open the fullscreen panel
-        if (plugin.mode === 'fullscreen') {
-            this.openFullscreenPlugin(plugin);
-            return;
-        }
-
-        // Inline mode — open the standard dialog
-        const dialog = document.getElementById('pluginDialog');
-        const title = document.getElementById('pluginDialogTitle');
-        const desc = document.getElementById('pluginDialogDesc');
-        const inputs = document.getElementById('pluginInputs');
-        
-        if (!dialog || !title || !desc || !inputs) return;
-        
-        title.textContent = plugin.icon + ' ' + plugin.name;
-        desc.textContent = plugin.description;
-        
-        inputs.innerHTML = '';
-        plugin.inputs.forEach(input => {
-            const inputGroup = document.createElement('div');
-            inputGroup.className = 'input-group';
-            inputGroup.innerHTML = `
-                <label>${input.label}</label>
-                <input type="text" 
-                       name="${input.name}" 
-                       value="${input.defaultValue || ''}"
-                       placeholder="${input.placeholder || ''}">
-            `;
-            inputs.appendChild(inputGroup);
-        });
-        
-        dialog.classList.add('show');
-    }
-
-    hidePluginDialog() {
-        const dialog = document.getElementById('pluginDialog');
-        if (dialog) dialog.classList.remove('show');
-        this.currentPlugin = null;
-    }
-
-    openFullscreenPlugin(plugin) {
-        const panel = document.getElementById('fullscreenPanel');
-        document.getElementById('fsPluginTitle').textContent = plugin.icon + ' ' + plugin.name;
-        panel.classList.add('show');
-
-        // Run the plugin in fullscreen mode
-        const pluginOutputs = [];
-        const ijsAPI = this.createIJSAPI(pluginOutputs);
-        this._lastIjsApi = ijsAPI;  // 保存引用供 MCP __tap 调试使用
-
-        // Create a default inputs object from plugin defaults
-        const inputValues = {};
-        if (plugin.inputs) {
-            plugin.inputs.forEach(function(inp) {
-                inputValues[inp.name] = inp.defaultValue || '';
-            });
-        }
-
-        // Fullscreen plugins don't return values — they use IJS.canvas/IJS.input
-        const func = new Function('inputs', 'IJS', plugin.jsCode + '\ncalculate(inputs, IJS);');
-        try {
-            func(inputValues, ijsAPI);
-        } catch (e) {
-            console.error('Fullscreen plugin error:', e);
-            ijsAPI.ui.close();
-            this.addOutput('插件错误: ' + e.message, 'error');
-            this.scrollToBottom();
-        }
-    }
-
-    executeCurrentPlugin() {
-        if (!this.currentPlugin) return;
-        
-        const inputsDiv = document.getElementById('pluginInputs');
-        if (!inputsDiv) return;
-        
-        const inputElements = inputsDiv.querySelectorAll('input');
-        const inputValues = {};
-        
-        // 收集输入值并转换为 Symja 可运算类型
-        inputElements.forEach(input => {
-            inputValues[input.name] = this.convertInputToSymjaType(input.value);
-        });
-        
-        try {
-            // 用于存储插件的控制台输出
-            const pluginOutputs = [];
-            
-            // 创建 IJS API 对象
-            const ijsAPI = this.createIJSAPI(pluginOutputs);
-
-            // 执行插件代码
-            const func = new Function('inputs', 'IJS', this.currentPlugin.jsCode + '\nreturn calculate(inputs, IJS);');
-            const result = func(inputValues, ijsAPI);
-            
-            // 验证返回值是否为有效的可运算类型
-            if (typeof result !== 'string' || result.trim() === '') {
-                throw new Error('插件必须返回非空字符串类型的 Symja 表达式');
-            }
-            
-            // 验证返回值是否是有效的 Symja 表达式(尝试计算)
-            try {
-                Android.symjaEval(result);
-            } catch (e) {
-                throw new Error('插件返回值必须是有效的 Symja 表达式,当前返回: ' + result);
-            }
-            
-            // 显示插件名称
-            this.addOutput(`${this.currentPlugin.icon} ${this.currentPlugin.name}`, 'input');
-            
-            // 显示插件的控制台输出(如果有)
-            if (pluginOutputs.length > 0) {
-                pluginOutputs.forEach(output => {
-                    this.addOutput(output, 'plugin-info');
-                });
-            }
-            
-            // 显示插件的返回值(可运算结果)
-            this.addOutput(result, 'plugin');
-            
-            // 将插件结果保存到 Symja 引擎,使其可以通过 % 引用
-            try {
-                Android.evaluate(`Out = ${result}`);
-            } catch (e) {
-                console.log('Plugin result storage error:', e);
-            }
-            
-            this.scrollToBottom();
-            this.hidePluginDialog();
-        } catch (e) {
-            this.addOutput(`插件执行错误: ${e.message}`, 'error');
-        }
-    }
-
-    scrollToBottom() {
-        this.output.scrollTop = this.output.scrollHeight;
-    }
-
-    escapeHtml(text) {
-        const div = document.createElement('div');
-        div.textContent = text;
-        return div.innerHTML;
-    }
-
-    hideKeyboard() {
-        // 移除输入框焦点
-        if (this.input) {
-            this.input.blur();
-        }
-
-        // 调用 Android 接口隐藏键盘
-        try {
-            if (typeof Android !== 'undefined' && Android.hideKeyboard) {
-                Android.hideKeyboard();
-            }
-        } catch (e) {
-            console.log('hideKeyboard not available:', e);
-        }
-    }
-
-    pollMcpEvents() {
-        try {
-            if (typeof Android === 'undefined' || !Android.getMcpEvents) return;
-            const json = Android.getMcpEvents();
-            if (!json || json === '[]') return;
-            const events = JSON.parse(json);
-            events.forEach(ev => {
-                // 调试命令:MCP 触发打开全屏插件
-                if (ev.expression.startsWith('##OPEN_PLUGIN##')) {
-                    var pid = ev.expression.substring(16);
-                    var pl = this.plugins.find(function(p) { return p.id === pid || p.command === pid; });
-                    if (pl) this.openPlugin(pl);
-                    return;
-                }
-                // __eval 结果回传
-                if (ev.expression === '__eval_result') {
-                    try {
-                        var er = JSON.parse(ev.result);
-                        if (er.ok) {
-                            this.addOutput('__eval → ' + JSON.stringify(er.result), 'mcp-result');
-                        } else {
-                            this.addOutput('__eval error: ' + er.error, 'error');
-                        }
-                    } catch(e) {
-                        this.addOutput('__eval raw: ' + ev.result, 'mcp-result');
-                    }
-                    return;
-                }
-
-                // 调试命令:模拟画布点击 __tap x y (逻辑坐标)
-                if (ev.expression.startsWith('__tap ')) {
-                    var parts = ev.expression.split(/\s+/);
-                    var tx = parseFloat(parts[1]), ty = parseFloat(parts[2]);
-                    if (!isNaN(tx) && !isNaN(ty)) {
-                        // 将逻辑坐标转换为屏幕 clientX/Y
-                        var c = document.querySelector('#fullscreenContent canvas');
-                        if (c) {
-                            var r = c.getBoundingClientRect();
-                            var cx = r.left + (tx / (parseInt(c.style.width) || r.width)) * r.width;
-                            var cy = r.top + (ty / (parseInt(c.style.height) || r.height)) * r.height;
-                            // 通过 _fireTouch 直接触发插件回调
-                            try {
-                                // 获取当前插件的 IJS API 实例,调用 _fireTouch
-                                var apiInstance = symjaConsole._lastIjsApi;
-                                if (apiInstance && apiInstance.input && apiInstance.input._fireTouch) {
-                                    apiInstance.input._fireTouch(cx, cy);
-                                }
-                            } catch(e) { console.log('_fireTouch error:', e); }
-                        }
-                    }
-                    return;
-                }
-                this.mcpEventCount++;
-                this.addToHistory(ev.expression);
-                this.addOutput(ev.expression, 'mcp-input', { seq: this.mcpEventCount });
-                // 检测 LaTeX 渲染
-                if (ev.result && ev.result.startsWith('##TEXFORM##')) {
-                    const latex = ev.result.substring(11);
-                    this.addOutput(latex, 'mcp-latex', { renderLatex: true });
-                }
-                // 检测绘图结果 → Canvas 渲染
-                else if (ev.result && ev.result.startsWith('{"type":"plot"')) {
-                    this.addPlotOutput(ev.result, this.mcpEventCount);
-                } else {
-                    this.addOutput(ev.result, 'mcp-result', { seq: this.mcpEventCount });
-                }
-            });
-            this.scrollToBottom();
-        } catch (e) {
-            // Ignore polling errors
-        }
-    }
-}
-
-// 初始化控制台
-let symjaConsole;
-document.addEventListener('DOMContentLoaded', () => {
-    symjaConsole = new SymjaConsole();
-});
-
-// ── Canvas 绘图渲染器 ────────────────────────────────────────
-class PlotRenderer {
-    constructor() {
-        this.canvas = null;
-        this.ctx = null;
-        this.data = null;
-        this.transform = { ox: 0, oy: 0, sx: 1, sy: 1 };
-        this.bounds = { xMin: 0, xMax: 1, yMin: 0, yMax: 1 };
-        this.dpr = window.devicePixelRatio || 1;
-        this.colors = [
-            [0.369, 0.507, 0.710], [0.851, 0.208, 0.125],
-            [0.267, 0.612, 0.329], [0.737, 0.349, 0.800],
-            [1.0, 0.608, 0.0]
-        ];
-    }
-
-    render(canvas, data) {
-        this.canvas = canvas;
-        this.ctx = canvas.getContext('2d');
-        this.data = data;
-
-        // 计算全局范围
-        this.bounds = this.computeBounds(data);
-        const padX = (this.bounds.xMax - this.bounds.xMin) * 0.05 || 0.5;
-        const padY = (this.bounds.yMax - this.bounds.yMin) * 0.05 || 0.5;
-        this.bounds.xMin -= padX;
-        this.bounds.xMax += padX;
-        this.bounds.yMin -= padY;
-        this.bounds.yMax += padY;
-
-        // 强制等比例:确保圆不变椭圆
-        const m = 60, cw = canvas.width - 2 * m, ch = canvas.height - 2 * m;
-        const xr = this.bounds.xMax - this.bounds.xMin;
-        const yr = this.bounds.yMax - this.bounds.yMin;
-        if (xr > 0 && yr > 0) {
-            if (xr / cw > yr / ch) {
-                const ny = xr * ch / cw;
-                const my = (this.bounds.yMin + this.bounds.yMax) / 2;
-                this.bounds.yMin = my - ny / 2;
-                this.bounds.yMax = my + ny / 2;
-            } else {
-                const nx = yr * cw / ch;
-                const mx = (this.bounds.xMin + this.bounds.xMax) / 2;
-                this.bounds.xMin = mx - nx / 2;
-                this.bounds.xMax = mx + nx / 2;
-            }
-        }
-
-        this.transform = { ox: 0, oy: 0, sx: 1, sy: 1 };
-        this.draw();
-
-        // 手势绑定
-        this.bindGestures();
-    }
-
-    computeBounds(data) {
-        let xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity;
-        if (!data.elements) return { xMin: -1, xMax: 1, yMin: -1, yMax: 1 };
-        for (const el of data.elements) {
-            if (!el.points) continue;
-            for (const pt of el.points) {
-                xMin = Math.min(xMin, pt[0]);
-                xMax = Math.max(xMax, pt[0]);
-                yMin = Math.min(yMin, pt[1]);
-                yMax = Math.max(yMax, pt[1]);
-            }
-        }
-        if (!isFinite(xMin)) return { xMin: -1, xMax: 1, yMin: -1, yMax: 1 };
-        return { xMin, xMax, yMin, yMax };
-    }
-
-    // ── 坐标变换 ──────────────────────────────────────
-    toCanvas(px, py) {
-        const w = this.canvas.width, h = this.canvas.height;
-        const m = 60; // 边距
-        const cw = w - 2 * m, ch = h - 2 * m;
-        const t = this.transform;
-        const b = this.bounds;
-        const xRange = b.xMax - b.xMin, yRange = b.yMax - b.yMin;
-        return {
-            x: m + (px - b.xMin - t.ox * xRange) / (xRange * t.sx) * cw,
-            y: h - m - (py - b.yMin - t.oy * yRange) / (yRange * t.sy) * ch
-        };
-    }
-
-    // ── 主绘制 ────────────────────────────────────────
-    draw() {
-        const ctx = this.ctx;
-        const w = this.canvas.width, h = this.canvas.height;
-        const m = 60;
-        const cw = w - 2 * m, ch = h - 2 * m;
-        const t = this.transform;
-        const b = this.bounds;
-
-        // 背景
-        ctx.fillStyle = '#FFFFFF';
-        ctx.fillRect(0, 0, w, h);
-
-        // 网格
-        this.drawGrid(m, cw, ch);
-
-        // 坐标轴
-        this.drawAxes(m, cw, ch);
-
-        // 数据曲线
-        if (this.data.elements) {
-            const useColors = this.colors;
-            this.data.elements.forEach((el, idx) => {
-                if (!el.points || el.points.length === 0) return;
-                const color = el.color || useColors[idx % useColors.length];
-                const rgb = `rgb(${Math.round(color[0]*255)},${Math.round(color[1]*255)},${Math.round(color[2]*255)})`;
-
-                if (el.type === 'line' || (el.type === 'point' && this.data.options && this.data.options.joined)) {
-                    ctx.strokeStyle = rgb;
-                    ctx.lineWidth = 3 * this.dpr;
-                    ctx.beginPath();
-                    for (let i = 0; i < el.points.length; i++) {
-                        const pt = this.toCanvas(el.points[i][0], el.points[i][1]);
-                        if (i === 0) ctx.moveTo(pt.x, pt.y);
-                        else ctx.lineTo(pt.x, pt.y);
-                    }
-                    ctx.stroke();
-                }
-
-                if (el.type === 'point') {
-                    ctx.fillStyle = rgb;
-                    for (const pt of el.points) {
-                        const p = this.toCanvas(pt[0], pt[1]);
-                        ctx.beginPath();
-                        ctx.arc(p.x, p.y, 3 * this.dpr, 0, Math.PI * 2);
-                        ctx.fill();
-                    }
-                }
-            });
-        }
-    }
-
-    drawGrid(m, cw, ch) {
-        const ctx = this.ctx;
-        const b = this.bounds;
-        const t = this.transform;
-        const xRange = b.xMax - b.xMin, yRange = b.yMax - b.yMin;
-
-        ctx.strokeStyle = '#E8EDF2';
-        ctx.lineWidth = Math.round(1 * this.dpr);
-        ctx.fillStyle = '#8B95A5';
-        ctx.font = '11px -apple-system, sans-serif';
-        ctx.textAlign = 'center';
-
-        const xStep = this.niceStep(xRange, 5);
-        const yStep = this.niceStep(yRange, 5);
-
-        // X 网格 + 标签
-        let xStart = Math.floor((b.xMin + t.ox * xRange) / xStep) * xStep;
-        for (let x = xStart; x <= b.xMin + t.ox * xRange + xRange / t.sx + xStep; x += xStep) {
-            const pt = this.toCanvas(x, 0);
-            if (pt.x >= m && pt.x <= m + cw) {
-                ctx.beginPath();
-                ctx.moveTo(pt.x, m);
-                ctx.lineTo(pt.x, m + ch);
-                ctx.stroke();
-                ctx.fillText(this.fmtNum(x), pt.x, m + ch + 16);
-            }
-        }
-
-        // Y 网格 + 标签
-        ctx.textAlign = 'right';
-        let yStart = Math.floor((b.yMin + t.oy * yRange) / yStep) * yStep;
-        for (let y = yStart; y <= b.yMin + t.oy * yRange + yRange / t.sy + yStep; y += yStep) {
-            const pt = this.toCanvas(0, y);
-            if (pt.y >= m && pt.y <= m + ch) {
-                ctx.beginPath();
-                ctx.moveTo(m, pt.y);
-                ctx.lineTo(m + cw, pt.y);
-                ctx.stroke();
-                ctx.fillText(this.fmtNum(y), m - 8, pt.y + 4);
-            }
-        }
-    }
-
-    drawAxes(m, cw, ch) {
-        const ctx = this.ctx;
-        const b = this.bounds;
-        const t = this.transform;
-        const origin = this.toCanvas(0, 0);
-
-        ctx.strokeStyle = '#1976D2';
-        ctx.lineWidth = Math.round(2 * this.dpr);
-        ctx.fillStyle = '#1976D2';
-        ctx.font = 'bold 11px -apple-system, sans-serif';
-
-        // X 轴
-        if (origin.y >= m && origin.y <= m + ch) {
-            ctx.beginPath();
-            ctx.moveTo(m, origin.y);
-            ctx.lineTo(m + cw, origin.y);
-            ctx.stroke();
-            // 箭头
-            ctx.beginPath();
-            ctx.moveTo(m + cw, origin.y);
-            ctx.lineTo(m + cw - 8, origin.y - 4);
-            ctx.lineTo(m + cw - 8, origin.y + 4);
-            ctx.fill();
-        }
-
-        // Y 轴
-        if (origin.x >= m && origin.x <= m + cw) {
-            ctx.beginPath();
-            ctx.moveTo(origin.x, m + ch);
-            ctx.lineTo(origin.x, m);
-            ctx.stroke();
-            // 箭头
-            ctx.beginPath();
-            ctx.moveTo(origin.x, m);
-            ctx.lineTo(origin.x - 4, m + 8);
-            ctx.lineTo(origin.x + 4, m + 8);
-            ctx.fill();
-        }
-
-        // 原点标签
-        if (origin.x >= m && origin.x <= m + cw && origin.y >= m && origin.y <= m + ch) {
-            ctx.textAlign = 'right';
-            ctx.fillText('O', origin.x - 6, origin.y + 14);
-        }
-    }
-
-    niceStep(range, target) {
-        const rough = range / target;
-        const exp = Math.pow(10, Math.floor(Math.log10(rough)));
-        const mant = rough / exp;
-        let nice = 1;
-        if (mant <= 1.5) nice = 1;
-        else if (mant <= 3.5) nice = 2;
-        else if (mant <= 7.5) nice = 5;
-        else nice = 10;
-        return nice * exp;
-    }
-
-    fmtNum(n) {
-        if (Math.abs(n) < 1e-10) return '0';
-        if (Math.abs(n) < 0.001 || Math.abs(n) >= 1e6) return n.toExponential(2);
-        if (Number.isInteger(n)) return n.toString();
-        const s = n.toFixed(4);
-        return s.replace(/0+$/, '').replace(/\.$/, '');
-    }
-
-    // ── 手势 ──────────────────────────────────────────
-    bindGestures() {
-        const canvas = this.canvas;
-        // 移除旧监听器防止泄露
-        if (this._touchHandlers) {
-            canvas.removeEventListener('touchstart', this._touchHandlers.start);
-            canvas.removeEventListener('touchmove', this._touchHandlers.move);
-            canvas.removeEventListener('touchend', this._touchHandlers.end);
-        }
-        let lastDist = 0, lastX = 0, lastY = 0, lastTap = 0, initialPinch = false;
-        const self = this;
-
-        const onStart = (e) => {
-            if (e.touches.length >= 2) {
-                const dx = e.touches[0].clientX - e.touches[1].clientX;
-                const dy = e.touches[0].clientY - e.touches[1].clientY;
-                lastDist = Math.sqrt(dx*dx + dy*dy);
-                initialPinch = true;
-            }
-            if (e.touches.length === 1 && !initialPinch) {
-                lastX = e.touches[0].clientX;
-                lastY = e.touches[0].clientY;
-            }
-        };
-        const onMove = (e) => {
-            if (e.touches.length >= 2 && initialPinch && lastDist > 0) {
-                const dx = e.touches[0].clientX - e.touches[1].clientX;
-                const dy = e.touches[0].clientY - e.touches[1].clientY;
-                const dist = Math.sqrt(dx*dx + dy*dy);
-                const scale = dist / lastDist;
-                if (scale > 0.3 && scale < 3) {
-                    self.transform.sx = Math.max(0.1, Math.min(20, self.transform.sx / scale));
-                    self.transform.sy = Math.max(0.1, Math.min(20, self.transform.sy / scale));
-                }
-                lastDist = dist;
-                self.draw();
-                e.preventDefault();
-            } else if (e.touches.length === 1 && !initialPinch) {
-                const dx = (e.touches[0].clientX - lastX) / canvas.clientWidth;
-                const dy = (e.touches[0].clientY - lastY) / canvas.clientHeight;
-                self.transform.ox -= dx / self.transform.sx;
-                self.transform.oy += dy / self.transform.sy;
-                lastX = e.touches[0].clientX;
-                lastY = e.touches[0].clientY;
-                self.draw();
-                e.preventDefault();
-            }
-        };
-        const onEnd = (e) => {
-            if (e.touches.length === 0) {
-                const now = Date.now();
-                if (initialPinch) {
-                    initialPinch = false;
-                } else if (now - lastTap < 350) {
-                    self.transform = { ox: 0, oy: 0, sx: 1, sy: 1 };
-                    self.draw();
-                }
-                lastTap = now;
-            }
-        };
-
-        canvas.addEventListener('touchstart', onStart, { passive: true });
-        canvas.addEventListener('touchmove', onMove, { passive: false });
-        canvas.addEventListener('touchend', onEnd, { passive: true });
-        this._touchHandlers = { start: onStart, move: onMove, end: onEnd };
-    }
-}
+// 控制台交互逻辑
+class SymjaConsole {
+    constructor() {
+        this.output = document.getElementById('output');
+        this.input = document.getElementById('input');
+        this.inputPrompt = document.getElementById('inputPrompt');
+        this.completionList = document.getElementById('completionList');
+        this.helpBtn = document.getElementById('helpBtn');
+        this.pluginsBtn = document.getElementById('pluginsBtn');
+        this.settingsBtn = document.getElementById('settingsBtn');
+        
+        // 历史记录
+        this.history = this.loadHistory();
+        this.historyIndex = this.history.length;
+        this.tempInput = '';
+        
+        // 插件相关
+        this.plugins = [];
+        this.currentPlugin = null;
+        
+        // 自动补全相关
+        this.completions = [];
+        this.completionIndex = -1;
+        
+        // 变量追踪
+        this.variables = new Set();
+        
+        // 执行计数器
+        this.executionCount = this.loadExecutionCount();
+
+        this.init();
+        this.loadPlugins();
+        this.refreshPluginList(); // 初始化时刷新插件列表
+        this.showWelcome();
+        this.updatePrompt();
+    }
+
+    init() {
+        // 输入框事件
+        this.input.addEventListener('keydown', (e) => this.handleKeyDown(e));
+        this.input.addEventListener('input', () => this.handleInput());
+
+        // 发送按钮
+        const sendBtn = document.getElementById('sendBtn');
+        if (sendBtn) {
+            sendBtn.addEventListener('click', () => this.evaluate());
+        }
+
+        // 顶部导航按钮
+        this.helpBtn?.addEventListener('click', () => this.showHelp());
+        this.pluginsBtn?.addEventListener('click', () => this.showPluginPanel());
+        this.settingsBtn?.addEventListener('click', () => this.showSettings());
+
+        // 设置面板
+        document.getElementById('closeSettings')?.addEventListener('click', () => this.hideSettings());
+        document.getElementById('applySettings')?.addEventListener('click', () => this.applySettings());
+
+        // 加载已保存的设置
+        this.loadSettings();
+
+        // MCP 事件轮询(仅在服务器开启时运行)
+        this.mcpEventCount = 0;
+        this.mcpPollTimer = null;
+        this.startMcpPollingIfNeeded();
+        
+        // 快捷键工具栏
+        this.initShortcutsBar();
+        
+        // 帮助页面关闭按钮
+        document.getElementById('closeHelp')?.addEventListener('click', () => {
+            this.hideHelp();
+        });
+
+        // 恢复默认插件
+        document.getElementById('restorePluginsBtn')?.addEventListener('click', () => {
+            try {
+                const result = Android.restoreDefaultPlugins();
+                this.loadPlugins();
+                this.refreshPluginList();
+                this.addOutput(result, 'help');
+                this.scrollToBottom();
+            } catch(e) {
+                this.addOutput('❌ 恢复失败: ' + e.message, 'error');
+            }
+        });
+
+        // 全屏面板关闭
+        document.getElementById('closeFullscreen')?.addEventListener('click', () => {
+            const fs = document.getElementById('fullscreenPanel');
+            fs.classList.remove('show');
+            // 调用插件的 IJS.ui.close() 进行清理(停止音乐、动画等)
+            if (this._lastIjsApi && this._lastIjsApi.ui) {
+                this._lastIjsApi.ui.close();
+            }
+            // 确保动画停止
+            if (this._lastIjsApi && this._lastIjsApi.canvas) {
+                this._lastIjsApi.canvas.stop();
+            }
+        });
+
+        // 插件面板事件
+        document.getElementById('closePluginPanel')?.addEventListener('click', () => {
+            this.hidePluginPanel();
+        });
+
+        document.getElementById('closePluginDialog')?.addEventListener('click', () => {
+            this.hidePluginDialog();
+        });
+
+        document.getElementById('cancelPlugin')?.addEventListener('click', () => {
+            this.hidePluginDialog();
+        });
+
+        document.getElementById('executePlugin')?.addEventListener('click', () => {
+            this.executeCurrentPlugin();
+        });
+    }
+
+    initShortcutsBar() {
+        const buttons = document.querySelectorAll('.shortcut-btn');
+        buttons.forEach(btn => {
+            btn.addEventListener('click', (e) => {
+                e.preventDefault();
+                const action = btn.dataset.action;
+                const value = btn.dataset.value;
+                
+                switch(action) {
+                    case 'bracket':
+                    case 'insert':
+                        this.insertText(value);
+                        break;
+                    case 'history-up':
+                        this.navigateHistory(-1);
+                        break;
+                    case 'history-down':
+                        this.navigateHistory(1);
+                        break;
+                }
+                
+                // 保持输入框焦点
+                this.input.focus();
+            });
+        });
+        
+        // 初始化插件标签页
+        this.initPluginTabs();
+        
+        // 初始化插件分享功能
+        this.initPluginShare();
+    }
+
+    initPluginTabs() {
+        const tabs = document.querySelectorAll('.plugin-tab');
+        const contents = document.querySelectorAll('.tab-content');
+        
+        tabs.forEach(tab => {
+            tab.addEventListener('click', () => {
+                const targetTab = tab.dataset.tab;
+                
+                // 切换标签激活状态
+                tabs.forEach(t => t.classList.remove('active'));
+                tab.classList.add('active');
+                
+                // 切换内容显示
+                contents.forEach(content => {
+                    if (content.dataset.content === targetTab) {
+                        content.classList.add('active');
+                    } else {
+                        content.classList.remove('active');
+                    }
+                });
+                
+                // 如果切换到导入/分享标签,更新插件列表
+                if (targetTab === 'import') {
+                    this.updatePluginSelects();
+                }
+            });
+        });
+    }
+
+    initPluginShare() {
+        // 使用事件委托,在document上监听点击事件
+        document.addEventListener('click', (e) => {
+            const target = e.target;
+            
+            // 生成分享码
+            if (target.id === 'generateShareCode') {
+                console.log('generateShareCode clicked');
+                e.preventDefault();
+                this.generateShareCode();
+            }
+            
+            // 复制分享码
+            if (target.id === 'copyShareCode') {
+                console.log('copyShareCode clicked');
+                e.preventDefault();
+                this.copyShareCode();
+            }
+            
+            // 导入插件
+            if (target.id === 'importPlugin') {
+                console.log('importPlugin clicked');
+                e.preventDefault();
+                this.importPlugin();
+            }
+            
+            // 删除插件
+            if (target.id === 'deletePlugin') {
+                console.log('deletePlugin clicked');
+                e.preventDefault();
+                this.deletePluginConfirm();
+            }
+        });
+    }
+
+    updatePluginSelects() {
+        const shareSelect = document.getElementById('sharePluginSelect');
+        const deleteSelect = document.getElementById('deletePluginSelect');
+        
+        if (shareSelect) {
+            shareSelect.innerHTML = '<option value="">选择插件...</option>';
+            this.plugins.forEach((plugin, index) => {
+                const option = document.createElement('option');
+                option.value = index;
+                option.textContent = `${plugin.icon} ${plugin.name}`;
+                shareSelect.appendChild(option);
+            });
+        }
+        
+        if (deleteSelect) {
+            deleteSelect.innerHTML = '<option value="">选择要删除的插件...</option>';
+            this.plugins.forEach((plugin, index) => {
+                const option = document.createElement('option');
+                option.value = index;
+                option.textContent = `${plugin.icon} ${plugin.name}`;
+                deleteSelect.appendChild(option);
+            });
+        }
+    }
+
+    generateShareCode() {
+        const select = document.getElementById('sharePluginSelect');
+        const output = document.getElementById('shareCodeOutput');
+        const copyBtn = document.getElementById('copyShareCode');
+        
+        if (!select || !output) return;
+        
+        const index = parseInt(select.value);
+        if (isNaN(index) || index < 0 || index >= this.plugins.length) {
+            this.addOutput('❌ 请先选择一个插件', 'error');
+            this.scrollToBottom();
+            return;
+        }
+        
+        const plugin = this.plugins[index];
+        const shareCode = JSON.stringify(plugin, null, 2);
+        
+        output.value = shareCode;
+        if (copyBtn) copyBtn.style.display = 'block';
+        
+        this.addOutput(`✅ 已生成 "${plugin.name}" 的分享码`, 'help');
+        this.scrollToBottom();
+    }
+
+    copyShareCode() {
+        const output = document.getElementById('shareCodeOutput');
+        if (!output || !output.value) return;
+        
+        output.select();
+        document.execCommand('copy');
+        
+        this.addOutput('📋 分享码已复制到剪贴板', 'help');
+        this.scrollToBottom();
+    }
+
+    importPlugin() {
+        const input = document.getElementById('importCodeInput');
+        if (!input || !input.value.trim()) {
+            this.addOutput('❌ 请粘贴插件代码', 'error');
+            this.scrollToBottom();
+            return;
+        }
+        
+        try {
+            const pluginData = JSON.parse(input.value);
+            
+            // 验证插件格式
+            if (!pluginData.id || !pluginData.name || !pluginData.jsCode) {
+                throw new Error('插件格式不正确,缺少必要字段');
+            }
+            
+            // 调用 Android 接口保存插件
+            try {
+                const result = Android.savePlugin(JSON.stringify(pluginData));
+                
+                // 重新加载插件
+                this.loadPlugins();
+                this.updatePluginSelects();
+                
+                // 刷新插件列表显示
+                this.refreshPluginList();
+                
+                // 显示成功消息
+                this.addOutput(`✅ ${result}`, 'help');
+                this.addOutput(`📦 插件 "${pluginData.name}" 已添加到列表`, 'help');
+                
+                // 清空输入
+                input.value = '';
+            } catch (e) {
+                this.addOutput(`❌ 保存失败: ${e.message}`, 'error');
+            }
+            
+            this.scrollToBottom();
+        } catch (e) {
+            this.addOutput(`❌ 解析失败: ${e.message}`, 'error');
+            this.scrollToBottom();
+        }
+    }
+
+    deletePluginConfirm() {
+        const select = document.getElementById('deletePluginSelect');
+        if (!select) {
+            console.error('deletePluginSelect element not found');
+            return;
+        }
+        
+        console.log('Select value:', select.value);
+        console.log('Plugins length:', this.plugins.length);
+        
+        const index = parseInt(select.value);
+        if (isNaN(index) || index < 0 || index >= this.plugins.length) {
+            this.addOutput('❌ 请先选择要删除的插件', 'error');
+            this.scrollToBottom();
+            return;
+        }
+        
+        const plugin = this.plugins[index];
+        console.log('Deleting plugin:', plugin.name, 'ID:', plugin.id);
+        
+        // 使用自定义确认对话框或直接删除
+        const confirmMessage = `确定要删除插件 "${plugin.name}" 吗?`;
+        console.log('Showing confirm dialog:', confirmMessage);
+        
+        let userConfirmed = false;
+        try {
+            userConfirmed = confirm(confirmMessage);
+            console.log('User confirmed:', userConfirmed);
+        } catch (e) {
+            console.error('Confirm dialog error:', e);
+            // 如果confirm失败,直接删除
+            userConfirmed = true;
+        }
+        
+        if (userConfirmed) {
+            console.log('Proceeding with deletion...');
+            try {
+                const result = Android.deletePlugin(plugin.id);
+                console.log('Delete result:', result);
+                this.addOutput(`✅ ${result}`, 'help');
+                
+                // 重新加载插件
+                this.loadPlugins();
+                this.updatePluginSelects();
+                
+                // 刷新插件列表显示
+                this.refreshPluginList();
+            } catch (e) {
+                console.error('Delete error:', e);
+                this.addOutput(`❌ 删除失败: ${e.message}`, 'error');
+            }
+            
+            this.scrollToBottom();
+        } else {
+            console.log('User cancelled deletion');
+        }
+    }
+
+    refreshPluginList() {
+        const list = document.getElementById('pluginList');
+        console.log('refreshPluginList called, list element:', list);
+        console.log('Current plugins count:', this.plugins.length);
+        
+        if (!list) return;
+        
+        list.innerHTML = '';
+        
+        if (this.plugins.length === 0) {
+            console.log('No plugins, showing empty message');
+            list.innerHTML = '<div class="no-plugins">暂无插件</div>';
+        } else {
+            console.log('Rendering', this.plugins.length, 'plugins');
+            this.plugins.forEach(plugin => {
+                const item = document.createElement('div');
+                item.className = 'plugin-item';
+                const commandInfo = plugin.command ? `<div class="plugin-command">命令: ${plugin.command}</div>` : '';
+                item.innerHTML = `
+                    <div class="plugin-icon">${plugin.icon}</div>
+                    <div class="plugin-info">
+                        <div class="plugin-name">${plugin.name}</div>
+                        <div class="plugin-desc">${plugin.description}</div>
+                        ${commandInfo}
+                    </div>
+                `;
+                item.addEventListener('click', () => this.openPlugin(plugin));
+                list.appendChild(item);
+            });
+            console.log('Finished rendering plugins, list children:', list.children.length);
+        }
+    }
+
+    insertText(text) {
+        const start = this.input.selectionStart;
+        const end = this.input.selectionEnd;
+        const value = this.input.value;
+        
+        // 插入文本
+        this.input.value = value.substring(0, start) + text + value.substring(end);
+        
+        // 设置光标位置
+        const newPos = start + text.length;
+        this.input.setSelectionRange(newPos, newPos);
+        
+        // 触发输入事件
+        this.handleInput();
+    }
+
+    handleKeyDown(e) {
+        if (e.key === 'Enter') {
+            e.preventDefault();
+            this.hideCompletionList();
+            this.evaluate();
+        } else if (e.key === 'ArrowUp') {
+            e.preventDefault();
+            this.hideCompletionList();
+            this.navigateHistory(-1);
+        } else if (e.key === 'ArrowDown') {
+            e.preventDefault();
+            this.hideCompletionList();
+            this.navigateHistory(1);
+        } else if (e.key === 'Escape') {
+            this.input.value = '';
+            this.completions = [];
+            this.completionIndex = -1;
+            this.hideCompletionList();
+        }
+    }
+
+    handleInput() {
+        // 更新自动补全列表
+        console.log('handleInput called, value:', this.input.value);
+        this.updateCompletionList();
+    }
+
+    showWelcome() {
+        const line = document.createElement('div');
+        line.className = 'terminal-line';
+        line.innerHTML = `<div class="welcome-message">
+            <h2>🧮 某人的计算器 v1.3</h2>
+            <p>基于 Symja 的交互式符号计算控制台</p>
+            <p><strong>快速开始:</strong></p>
+            <p><code>2+2</code> 算术运算</p>
+            <p><code>D[x^3, x]</code> 求导</p>
+            <p><code>Integrate[x^2, {x,0,1}]</code> 定积分</p>
+            <p><code>Solve[x^2-5*x+6==0, x]</code> 解方程</p>
+            <p><code>Plot[Sin[x], {x,0,2*Pi}]</code> 函数绘图</p>
+            <p style="margin-top:12px">输入 <code>help</code> 查看完整文档 · 输入 <code>plugins</code> 管理插件</p>
+        </div>`;
+        this.output.appendChild(line);
+    }
+
+    evaluate() {
+        let expression = this.input.value.trim();
+
+        if (!expression) {
+            return;
+        }
+
+        // 处理特殊命令
+        if (this.handleSpecialCommand(expression)) {
+            this.addToHistory(expression);
+            this.input.value = '';
+            return;
+        }
+
+        // 添加到历史记录
+        this.addToHistory(expression);
+
+        // 显示输入(带计数器)
+        this.addOutput(expression, 'input', { count: this.executionCount });
+
+        // 调用Android接口进行计算
+        try {
+            const result = Android.evaluatePlot(expression);
+
+            // 检测是否是 Graphics/绘图结果
+            if (typeof result === 'string' && result.startsWith('{"type":"plot"')) {
+                this.addPlotOutput(result, this.executionCount);
+            }
+            // LaTeX 渲染
+            else if (result.startsWith('##TEXFORM##')) {
+                const latex = result.substring(11);
+                this.addOutput(latex, 'result', { renderLatex: true, count: this.executionCount });
+                this.scrollToBottom();
+            }
+            // 普通文本结果
+            else {
+                this.addOutput(result, 'result', { count: this.executionCount });
+            }
+
+            // 追踪变量
+            this.trackVariables(expression);
+        } catch (e) {
+            this.addOutput('错误: 无法连接到计算引擎', 'error');
+        }
+
+        // 增加计数器
+        this.executionCount++;
+        this.saveExecutionCount();
+        this.updatePrompt();
+
+        // 清空输入框
+        this.input.value = '';
+        this.completions = [];
+        this.completionIndex = -1;
+
+        // 滚动到底部
+        this.scrollToBottom();
+    }
+
+    addPlotOutput(plotJson, count, labels) {
+        const data = JSON.parse(plotJson);
+        const line = document.createElement('div');
+        line.className = 'terminal-line';
+
+        const container = document.createElement('div');
+        container.className = 'plot-container';
+
+        const canvas = document.createElement('canvas');
+        canvas.className = 'plot-canvas';
+        const dpr = window.devicePixelRatio || 1;
+        canvas.width = 500 * dpr;
+        canvas.height = 500 * dpr;
+        canvas.style.width = '100%';
+        canvas.style.height = '100%';
+        container.appendChild(canvas);
+
+        // 长按保存(单指静止800ms触发)
+        let longPressTimer;
+        canvas.addEventListener('touchstart', (e) => {
+            clearTimeout(longPressTimer);
+            if (e.touches.length === 1) {
+                longPressTimer = setTimeout(() => this.savePlot(canvas), 800);
+            }
+        });
+        canvas.addEventListener('touchend', () => clearTimeout(longPressTimer));
+        canvas.addEventListener('touchmove', () => clearTimeout(longPressTimer));
+
+        line.appendChild(container);
+        this.output.appendChild(line);
+
+        if (!this.plotRenderer) {
+            this.plotRenderer = new PlotRenderer();
+        }
+        this.plotRenderer.render(canvas, data);
+    }
+
+    buildLegend(data, labels) {
+        const div = document.createElement('div');
+        div.className = 'plot-legend';
+        const colors = [
+            [0.369,0.507,0.710],[0.851,0.208,0.125],[0.267,0.612,0.329],
+            [0.737,0.349,0.800],[1.0,0.608,0.0]
+        ];
+        data.elements.forEach((el, i) => {
+            const c = el.color || colors[i % colors.length];
+            const rgb = `rgb(${Math.round(c[0]*255)},${Math.round(c[1]*255)},${Math.round(c[2]*255)})`;
+            const item = document.createElement('span');
+            item.className = 'legend-item';
+            item.innerHTML = `<span class="legend-swatch" style="background:${rgb}"></span>${labels ? labels[i] : '曲线'+(i+1)}`;
+            div.appendChild(item);
+        });
+        return div;
+    }
+
+    savePlot(canvas) {
+        try {
+            const dataUrl = canvas.toDataURL('image/png');
+            const base64 = dataUrl.substring(dataUrl.indexOf(',') + 1);
+            const filename = 'IJS_Plot_' + new Date().toISOString().replace(/[:.]/g, '-') + '.png';
+            const result = Android.savePlotImage(base64, filename);
+            this.addOutput(result, 'help');
+            this.scrollToBottom();
+        } catch (e) {
+            this.addOutput('保存失败: ' + e.message, 'error');
+            this.scrollToBottom();
+        }
+    }
+
+    handleSpecialCommand(cmd) {
+        const lower = cmd.toLowerCase();
+        
+        if (lower === 'help' || lower === '?') {
+            this.showHelp();
+            return true;
+        }
+        if (lower === 'clear' || lower === 'cls') {
+            this.clear();
+            return true;
+        }
+        if (lower === 'history') {
+            this.showHistory();
+            return true;
+        }
+        if (lower === 'plugins') {
+            this.showPluginPanel();
+            return true;
+        }
+        if (lower === 'mcp') {
+            this.showMcpAddress();
+            return true;
+        }
+        // 调试:通过MCP evaluate("__open plugin_id")打开全屏插件
+        if (lower.startsWith('__open ')) {
+            var pid = lower.substring(7).trim();
+            var pl = this.plugins.find(function(p) { return p.id === pid || p.command === pid; });
+            if (pl) this.openPlugin(pl);
+            else this.addOutput('插件未找到: ' + pid, 'error');
+            return true;
+        }
+        if (lower === 'feedback') {
+            this.showFeedback();
+            return true;
+        }
+        if (lower.startsWith('clear ')) {
+            const varName = cmd.substring(6).trim();
+            this.clearVariable(varName);
+            return true;
+        }
+        
+        // 检查是否是插件命令
+        if (this.handlePluginCommand(lower)) {
+            return true;
+        }
+        
+        return false;
+    }
+
+    handlePluginCommand(cmd) {
+        // 检查是否是带参数的插件命令格式: command[arg1, arg2, ...]
+        const paramMatch = cmd.match(/^([a-z0-9_-]+)\[(.*)\]$/i);
+        
+        if (paramMatch) {
+            const commandName = paramMatch[1].toLowerCase();
+            const argsString = paramMatch[2];
+            
+            // 查找匹配的插件
+            const plugin = this.plugins.find(p => p.command && p.command.toLowerCase() === commandName);
+            
+            if (plugin) {
+                // 解析参数
+                const args = this.parsePluginArgs(argsString);
+                
+                // 验证参数数量
+                if (args.length !== plugin.inputs.length) {
+                    this.addOutput(`❌ 参数数量不匹配。${plugin.name} 需要 ${plugin.inputs.length} 个参数,但提供了 ${args.length} 个`, 'error');
+                    this.scrollToBottom();
+                    return true;
+                }
+                
+                // 执行插件(直接模式)
+                this.executePluginDirect(plugin, args);
+                return true;
+            }
+        }
+        
+        // 查找匹配的插件命令(无参数,打开对话框)
+        for (const plugin of this.plugins) {
+            if (plugin.command && plugin.command.toLowerCase() === cmd) {
+                this.addOutput(`🔌 打开插件: ${plugin.name}`, 'help');
+                this.scrollToBottom();
+                this.openPlugin(plugin);
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    parsePluginArgs(argsString) {
+        // 解析逗号分隔的参数,支持引号、大括号、方括号内的逗号
+        const args = [];
+        let current = '';
+        let inQuotes = false;
+        let quoteChar = '';
+        let bracketDepth = 0; // 跟踪 {} 的嵌套深度
+        let squareBracketDepth = 0; // 跟踪 [] 的嵌套深度
+        
+        for (let i = 0; i < argsString.length; i++) {
+            const char = argsString[i];
+            
+            // 处理引号
+            if ((char === '"' || char === "'") && (i === 0 || argsString[i-1] !== '\\')) {
+                if (!inQuotes) {
+                    inQuotes = true;
+                    quoteChar = char;
+                } else if (char === quoteChar) {
+                    inQuotes = false;
+                    quoteChar = '';
+                }
+                current += char;
+            }
+            // 处理大括号
+            else if (char === '{' && !inQuotes) {
+                bracketDepth++;
+                current += char;
+            }
+            else if (char === '}' && !inQuotes) {
+                bracketDepth--;
+                current += char;
+            }
+            // 处理方括号
+            else if (char === '[' && !inQuotes) {
+                squareBracketDepth++;
+                current += char;
+            }
+            else if (char === ']' && !inQuotes) {
+                squareBracketDepth--;
+                current += char;
+            }
+            // 处理逗号分隔符
+            else if (char === ',' && !inQuotes && bracketDepth === 0 && squareBracketDepth === 0) {
+                args.push(current.trim().replace(/^["']|["']$/g, ''));
+                current = '';
+            }
+            else {
+                current += char;
+            }
+        }
+        
+        if (current.trim()) {
+            args.push(current.trim().replace(/^["']|["']$/g, ''));
+        }
+        
+        return args;
+    }
+    
+    executePluginDirect(plugin, args) {
+        try {
+            console.log('executePluginDirect called with args:', args);
+            
+            // 构建输入对象,并转换为 Symja 可运算类型
+            const inputValues = {};
+            plugin.inputs.forEach((input, index) => {
+                const originalArg = args[index];
+                const convertedArg = this.convertInputToSymjaType(originalArg);
+                console.log(`Converting arg[${index}]: "${originalArg}" -> "${convertedArg}"`);
+                inputValues[input.name] = convertedArg;
+            });
+            
+            console.log('Final inputValues:', inputValues);
+            
+            // 用于存储插件的控制台输出
+            const pluginOutputs = [];
+
+            const ijsAPI = this.createIJSAPI(pluginOutputs);
+            const func = new Function('inputs', 'IJS', plugin.jsCode + '\nreturn calculate(inputs, IJS);');
+            const result = func(inputValues, ijsAPI);
+            
+            console.log('Plugin result:', result);
+            
+            // 验证返回值
+            if (typeof result !== 'string' || result.trim() === '') {
+                throw new Error('插件必须返回非空字符串类型的 Symja 表达式');
+            }
+            
+            // 验证返回值是否是有效的 Symja 表达式
+            try {
+                Android.symjaEval(result);
+            } catch (e) {
+                throw new Error('插件返回值必须是有效的 Symja 表达式,当前返回: ' + result);
+            }
+            
+            // 显示插件名称和参数
+            const argsDisplay = args.map((arg, i) => `${plugin.inputs[i].name}=${arg}`).join(', ');
+            this.addOutput(`${plugin.icon} ${plugin.name}[${argsDisplay}]`, 'input', { count: this.executionCount });
+            
+            // 显示插件的控制台输出(如果有)
+            if (pluginOutputs.length > 0) {
+                pluginOutputs.forEach(output => {
+                    this.addOutput(output, 'plugin-info');
+                });
+            }
+            
+            // 显示插件的返回值(可运算结果)
+            this.addOutput(result, 'result', { count: this.executionCount });
+            
+            // 将插件结果保存到 Symja 引擎,使其可以通过 % 引用
+            try {
+                Android.evaluate(`Out = ${result}`);
+            } catch (e) {
+                console.log('Plugin result storage error:', e);
+            }
+            
+            // 增加计数器
+            this.executionCount++;
+            this.saveExecutionCount();
+            this.updatePrompt();
+            
+            this.scrollToBottom();
+        } catch (e) {
+            console.log('Plugin execution error:', e);
+            this.addOutput(`插件执行错误: ${e.message}`, 'error');
+            this.scrollToBottom();
+        }
+    }
+    
+    // 将用户输入转换为 Symja 可运算的类型
+    convertInputToSymjaType(value) {
+        console.log(`convertInputToSymjaType called with: "${value}"`);
+        
+        if (!value || typeof value !== 'string') {
+            console.log('Value is not a string, returning as-is');
+            return value;
+        }
+        
+        const trimmed = value.trim();
+        
+        // 空值直接返回
+        if (trimmed === '') {
+            console.log('Value is empty, returning as-is');
+            return trimmed;
+        }
+        
+        // 检查是否是单个变量名(字母开头,后跟字母数字下划线)或 % 符号
+        const varNameMatch = trimmed.match(/^([a-zA-Z]\w*|%)$/);
+        console.log(`Variable name match result:`, varNameMatch);
+        
+        if (varNameMatch) {
+            // 直接尝试求值,如果是已定义的变量会返回值,否则返回变量名本身
+            try {
+                const evalResult = Android.symjaEval(trimmed);
+                console.log(`Evaluated "${trimmed}" to: "${evalResult}"`);
+                
+                // 如果求值结果与输入不同,说明变量已定义
+                if (evalResult !== trimmed) {
+                    console.log(`Variable ${trimmed} is defined, using value: ${evalResult}`);
+                    return evalResult;
+                } else {
+                    console.log(`Variable ${trimmed} is not defined or equals itself`);
+                }
+            } catch (e) {
+                console.error(`Failed to evaluate ${trimmed}:`, e);
+            }
+        }
+        
+        // 尝试通过 Symja 验证,如果能成功解析说明是有效的表达式
+        console.log(`Trying to validate as Symja expression: "${trimmed}"`);
+        try {
+            Android.symjaEval(trimmed);
+            console.log(`Valid Symja expression, returning: "${trimmed}"`);
+            return trimmed;
+        } catch (e) {
+            // 如果无法解析为 Symja 表达式,返回原值
+            // 插件内部可以根据需要进一步处理
+            console.log(`Not a valid Symja expression, returning as literal: "${trimmed}"`);
+            return trimmed;
+        }
+    }
+    
+    createIJSAPI(pluginOutputs) {
+        const self = this;
+        // 闭包引用 — IJS.input.xy() 需要访问 canvas 对象
+        const canvasRef = { _canvas: null, _logicalW: 600, _logicalH: 800 };
+        return {
+            // ── IJS.Symja 数学引擎 ─────────────────────────
+            Symja: {
+                print: function(message) {
+                    pluginOutputs.push(String(message));
+                },
+                eval: function(expression) {
+                    try { return Android.symjaEval(expression); }
+                    catch (e) { return '错误: 无法调用 Symja'; }
+                },
+                differentiate: function(expr, variable) { return this.eval(`D[${expr}, ${variable}]`); },
+                integrate: function(expr, variable) { return this.eval(`Integrate[${expr}, ${variable}]`); },
+                limit: function(expr, variable, point, direction) {
+                    try { return Android.limit(expr, variable, point, direction || ''); } catch (e) { return '错误:' + e.message; }
+                },
+                series: function(expr, variable, point, order) {
+                    try { return Android.series(expr, variable, point, order || 5); } catch (e) { return '错误:' + e.message; }
+                },
+                solve: function(equation, variable) { return this.eval(`Solve[${equation}, ${variable}]`); },
+                simplify: function(expr) { return this.eval(`Simplify[${expr}]`); },
+                expand: function(expr) { return this.eval(`Expand[${expr}]`); },
+                factor: function(expr) { return this.eval(`Factor[${expr}]`); },
+                apart: function(expr, variable) { try { return Android.apart(expr, variable); } catch (e) { return '错误:' + e.message; } },
+                together: function(expr) { try { return Android.together(expr); } catch (e) { return '错误:' + e.message; } },
+                trigSimplify: function(expr) { try { return Android.trigSimplify(expr); } catch (e) { return '错误:' + e.message; } },
+                sum: function(expr, variable, start, end) {
+                    try { return Android.sum(expr, variable, String(start), String(end)); } catch (e) { return '错误:' + e.message; }
+                },
+                product: function(expr, variable, start, end) {
+                    try { return Android.product(expr, variable, String(start), String(end)); } catch (e) { return '错误:' + e.message; }
+                },
+                matrix: {
+                    det: function(matrix) { try { return Android.matrixOp('Det', matrix); } catch (e) { return '错误:' + e.message; } },
+                    inverse: function(matrix) { try { return Android.matrixOp('Inverse', matrix); } catch (e) { return '错误:' + e.message; } },
+                    transpose: function(matrix) { try { return Android.matrixOp('Transpose', matrix); } catch (e) { return '错误:' + e.message; } },
+                    eigenvalues: function(matrix) { try { return Android.matrixOp('Eigenvalues', matrix); } catch (e) { return '错误:' + e.message; } },
+                    rank: function(matrix) { try { return Android.matrixOp('MatrixRank', matrix); } catch (e) { return '错误:' + e.message; } }
+                },
+                gcd: function(a, b) { try { return Android.gcd(String(a), String(b)); } catch (e) { return '错误:' + e.message; } },
+                lcm: function(a, b) { try { return Android.lcm(String(a), String(b)); } catch (e) { return '错误:' + e.message; } },
+                factorInteger: function(n) { try { return Android.factorInteger(String(n)); } catch (e) { return '错误:' + e.message; } },
+                isPrime: function(n) { try { return Android.isPrime(String(n)); } catch (e) { return '错误:' + e.message; } },
+                fibonacci: function(n) { try { return Android.fibonacci(n); } catch (e) { return '错误:' + e.message; } },
+                binomial: function(n, k) { try { return Android.binomial(String(n), String(k)); } catch (e) { return '错误:' + e.message; } },
+                numeric: function(expr, precision) {
+                    try { return Android.numericEval(expr, precision || 16); } catch (e) { return '错误:' + e.message; }
+                },
+                plot: function(func, variable, xMin, xMax) {
+                    try { return Android.evaluatePlot('Plot[' + func + ', {' + variable + ', ' + xMin + ', ' + xMax + '}]'); } catch (e) { return 'Error:' + e.message; }
+                },
+                parametricPlot: function(xf, yf, t, tMin, tMax) {
+                    try { return Android.evaluatePlot('ParametricPlot[{' + xf + ', ' + yf + '}, {' + t + ', ' + tMin + ', ' + tMax + '}]'); } catch (e) { return 'Error:' + e.message; }
+                },
+                listPlot: function(points) {
+                    try { return Android.evaluatePlot('ListPlot[' + points + ']'); } catch (e) { return 'Error:' + e.message; }
+                },
+                toTeX: function(expr) { try { return Android.toTeX(expr); } catch (e) { return '错误:' + e.message; } },
+                renderLatex: function(latex) { symjaConsole.addOutput(latex, 'result', { renderLatex: true }); return '已渲染 LaTeX'; }
+            },
+
+            // ── IJS.canvas 绘图 ────────────────────────────
+            canvas: {
+                _ctx: null,
+                _canvas: null,
+                _frameId: null,
+                create: function(w, h) {
+                    const panel = document.getElementById('fullscreenContent');
+                    if (!panel) return null;
+                    panel.innerHTML = '';
+                    const c = document.createElement('canvas');
+                    const dpr = window.devicePixelRatio || 1;
+                    // 自适应:未指定尺寸则填满全屏面板
+                    const cw = w || panel.clientWidth || window.innerWidth;
+                    const ch = h || panel.clientHeight || window.innerHeight;
+                    c.width = cw * dpr;
+                    c.height = ch * dpr;
+                    c.style.width = cw + 'px';
+                    c.style.height = ch + 'px';
+                    c.style.maxWidth = '100%';
+                    c.style.maxHeight = '100%';
+                    this._canvas = c;
+                    this._ctx = c.getContext('2d');
+                    this._ctx.scale(dpr, dpr);
+                    this._logicalW = cw;
+                    this._logicalH = ch;
+                    // 同步给 input.xy() 的闭包引用
+                    canvasRef._canvas = c;
+                    canvasRef._logicalW = cw;
+                    canvasRef._logicalH = ch;
+                    panel.appendChild(c);
+                    return this._ctx;
+                },
+                /** 获取逻辑画布宽度(供插件做自适应布局) */
+                width: function() { return this._logicalW || 600; },
+                /** 获取逻辑画布高度 */
+                height: function() { return this._logicalH || 800; },
+                getContext: function() { return this._ctx; },
+                getSize: function() {
+                    const dpr = window.devicePixelRatio || 1;
+                    return this._canvas ? { width: this._canvas.width / dpr, height: this._canvas.height / dpr } : { width: 0, height: 0 };
+                },
+                clear: function() {
+                    if (this._ctx && this._canvas) {
+                        const dpr = window.devicePixelRatio || 1;
+                        this._ctx.clearRect(0, 0, this._canvas.width / dpr, this._canvas.height / dpr);
+                    }
+                },
+                fill: function(color) {
+                    if (this._ctx && this._canvas) {
+                        const dpr = window.devicePixelRatio || 1;
+                        this._ctx.fillStyle = color || '#FFFFFF';
+                        this._ctx.fillRect(0, 0, this._canvas.width / dpr, this._canvas.height / dpr);
+                    }
+                },
+                size: function(w, h) {
+                    if (this._canvas) {
+                        this._canvas.width = w;
+                        this._canvas.height = h;
+                        this._logicalW = w;
+                        this._logicalH = h;
+                        canvasRef._logicalW = w;
+                        canvasRef._logicalH = h;
+                    }
+                },
+                // 动画循环
+                loop: function(callback) {
+                    const selfCanvas = this;
+                    function frame() {
+                        selfCanvas._frameId = requestAnimationFrame(frame);
+                        callback();
+                    }
+                    this.stop();
+                    selfCanvas._frameId = requestAnimationFrame(frame);
+                },
+                stop: function() {
+                    if (this._frameId) {
+                        cancelAnimationFrame(this._frameId);
+                        this._frameId = null;
+                    }
+                }
+            },
+
+            // ── IJS.input 触摸交互 ──────────────────────────
+            input: {
+                _callbacks: {},
+                onTouchStart: function(cb) { this._callbacks.touchstart = cb; },
+                onTouchMove: function(cb) { this._callbacks.touchmove = cb; },
+                onTouchEnd: function(cb) { this._callbacks.touchend = cb; this._bind(); },
+                /** 模拟触摸(MCP 调试用) */
+                _fireTouch: function(x, y) {
+                    if (this._callbacks.touchend) {
+                        this._callbacks.touchend({
+                            changedTouches: [{clientX: x, clientY: y}],
+                            preventDefault: function(){}
+                        });
+                    }
+                },
+                /** 将触摸事件的 clientX/Y 转换为画布逻辑坐标 */
+                xy: function(e) {
+                    var touch = e.changedTouches ? e.changedTouches[0] : (e.touches ? e.touches[0] : e);
+                    if (!touch) return {x:0,y:0};
+                    var c = canvasRef._canvas;
+                    if (!c) return {x: touch.clientX, y: touch.clientY};
+                    var r = c.getBoundingClientRect();
+                    return {
+                        x: (touch.clientX - r.left) * (canvasRef._logicalW / r.width),
+                        y: (touch.clientY - r.top) * (canvasRef._logicalH / r.height)
+                    };
+                },
+                _bound: false,
+                _bind: function() {
+                    if (this._bound) return;
+                    this._bound = true;
+                    const panel = document.getElementById('fullscreenContent');
+                    if (!panel) return;
+                    const selfInput = this;
+                    panel.addEventListener('touchstart', function(e) {
+                        e.preventDefault();
+                        if (selfInput._callbacks.touchstart) selfInput._callbacks.touchstart(e);
+                    }, { passive: false });
+                    panel.addEventListener('touchmove', function(e) {
+                        e.preventDefault();
+                        if (selfInput._callbacks.touchmove) selfInput._callbacks.touchmove(e);
+                    }, { passive: false });
+                    panel.addEventListener('touchend', function(e) {
+                        if (selfInput._callbacks.touchend) selfInput._callbacks.touchend(e);
+                    });
+                }
+            },
+
+            // ── IJS.io 文件读写 (应用数据目录) ────────────────
+            io: {
+                read: function(filename) {
+                    try { return Android.ioRead(filename); } catch (e) { return null; }
+                },
+                write: function(filename, content) {
+                    try { Android.ioWrite(filename, content); return true; } catch (e) { return false; }
+                },
+                list: function() {
+                    try { return JSON.parse(Android.ioList()); } catch (e) { return []; }
+                },
+                delete: function(filename) {
+                    try { Android.ioDelete(filename); return true; } catch (e) { return false; }
+                }
+            },
+
+            // ── IJS.audio 音效 ──────────────────────────────
+            audio: {
+                _ctx: null,
+                _getCtx: function() {
+                    if (!this._ctx) this._ctx = new (window.AudioContext || window.webkitAudioContext)();
+                    return this._ctx;
+                },
+                beep: function(freq, duration, type) {
+                    try {
+                        const ctx = this._getCtx();
+                        const osc = ctx.createOscillator();
+                        const gain = ctx.createGain();
+                        osc.type = type || 'square';
+                        osc.frequency.value = freq || 440;
+                        gain.gain.value = 0.15;
+                        gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + (duration || 0.2));
+                        osc.connect(gain);
+                        gain.connect(ctx.destination);
+                        osc.start(ctx.currentTime);
+                        osc.stop(ctx.currentTime + (duration || 0.2));
+                    } catch(e) {}
+                },
+                playNote: function(note, octave, duration) {
+                    const notes = { C: 262, D: 294, E: 330, F: 349, G: 392, A: 440, B: 494 };
+                    const freq = (notes[note] || 440) * Math.pow(2, (octave || 4) - 4);
+                    this.beep(freq, duration || 0.2, 'square');
+                },
+                playMelody: function(melody, tempo) {
+                    let t = 0;
+                    const beat = (tempo || 120) / 60;
+                    const selfAudio = this;
+                    melody.forEach(function(m) {
+                        if (m.note === 'R') { t += m.dur / beat; return; }
+                        setTimeout(function() {
+                            selfAudio.playNote(m.note, m.octave || 4, m.dur / beat * 0.9);
+                        }, t * 1000);
+                        t += m.dur / beat;
+                    });
+                },
+                playFile: function(filename) {
+                    // 暂不支持,留待扩展
+                }
+            },
+
+            // ── IJS.ui 面板控制 ─────────────────────────────
+            ui: {
+                close: function() {
+                    document.getElementById('fullscreenPanel').classList.remove('show');
+                },
+                setBackground: function(color) {
+                    const panel = document.getElementById('fullscreenContent');
+                    if (panel) panel.style.background = color;
+                }
+            }
+        };
+    }
+
+    trackVariables(expression) {
+        // 简单的变量赋值检测
+        const match = expression.match(/^([a-zA-Z]\w*)\s*=/);
+        if (match) {
+            this.variables.add(match[1]);
+        }
+    }
+
+    clearVariable(varName) {
+        try {
+            const result = Android.clearVariable(varName);
+            this.addOutput(result, 'help');
+            this.variables.delete(varName);
+        } catch (e) {
+            this.addOutput(`❌ 清除失败: ${e.message}`, 'error');
+        }
+        this.scrollToBottom();
+    }
+
+    showHistory() {
+        if (this.history.length === 0) {
+            this.addOutput('📜 历史记录为空', 'help');
+        } else {
+            const recent = this.history.slice(-10).reverse();
+            const historyText = recent.map((cmd, i) => `${recent.length - i}. ${cmd}`).join('\n');
+            this.addOutput(`📜 最近的命令:\n${historyText}`, 'help');
+        }
+        this.scrollToBottom();
+    }
+
+    showMcpAddress() {
+        try {
+            const running = typeof Android !== 'undefined' && Android.isMcpRunning && Android.isMcpRunning();
+            const port = '8734';
+            const status = running ? '✅ 运行中' : '⭕ 未启动';
+            const rikkaConfig = JSON.stringify({
+                mcpServers: {
+                    ijs: {
+                        type: "streamable-http",
+                        url: `http://127.0.0.1:${port}/mcp`,
+                        commonOptions: {
+                            name: "IJS - Symja 符号计算",
+                            enable: true,
+                            headers: []
+                        }
+                    }
+                }
+            }, null, 2);
+            this.addOutput(`🌐 MCP 服务器 ${status}\n\n📍 地址: http://127.0.0.1:${port}/mcp\n\n📋 RikkaHub 配置:\n${rikkaConfig}\n\n💻 Claude Desktop 配置:\n{"mcpServers":{"ijs":{"url":"http://127.0.0.1:${port}/mcp"}}}\n\n🔧 adb 转发: adb forward tcp:${port} tcp:${port}`, 'help');
+        } catch(e) {
+            this.addOutput('🌐 MCP 服务器\n   地址: http://127.0.0.1:8734/mcp', 'help');
+        }
+        this.scrollToBottom();
+    }
+
+    showFeedback() {
+        const email = 'nonvioce@qq.com';
+        const feedbackId = 'feedback-' + Date.now();
+        
+        // 创建带复制按钮的反馈信息
+        const line = document.createElement('div');
+        line.className = 'terminal-line';
+        line.innerHTML = `
+            <div class="feedback-line">
+                <div class="feedback-icon">📧</div>
+                <div class="feedback-content">
+                    <div class="feedback-title">反馈与建议</div>
+                    <div class="feedback-text">
+                        欢迎通过邮件向我们反馈问题或提出建议!
+                    </div>
+                    <div class="feedback-email-container">
+                        <span class="feedback-email">${email}</span>
+                        <button class="feedback-copy-btn" onclick="symjaConsole.copyFeedbackEmail('${email}', this)">
+                            📋 复制邮箱
+                        </button>
+                    </div>
+                </div>
+            </div>
+        `;
+        
+        this.output.appendChild(line);
+        this.scrollToBottom();
+    }
+
+    copyFeedbackEmail(email, button) {
+        // 创建临时文本区域
+        const textarea = document.createElement('textarea');
+        textarea.value = email;
+        textarea.style.position = 'fixed';
+        textarea.style.opacity = '0';
+        document.body.appendChild(textarea);
+        textarea.select();
+        
+        try {
+            document.execCommand('copy');
+            button.textContent = '✅ 已复制';
+            button.style.background = '#4CAF50';
+            setTimeout(() => {
+                button.textContent = '📋 复制邮箱';
+                button.style.background = '';
+            }, 2000);
+        } catch (e) {
+            console.error('Copy failed:', e);
+            button.textContent = '❌ 复制失败';
+            setTimeout(() => {
+                button.textContent = '📋 复制邮箱';
+            }, 2000);
+        }
+        
+        document.body.removeChild(textarea);
+    }
+
+    addToHistory(cmd) {
+        // 避免重复的连续命令
+        if (this.history.length === 0 || this.history[this.history.length - 1] !== cmd) {
+            this.history.push(cmd);
+            // 限制历史记录大小
+            if (this.history.length > 100) {
+                this.history.shift();
+            }
+            this.saveHistory();
+        }
+        this.historyIndex = this.history.length;
+    }
+
+    loadHistory() {
+        try {
+            if (typeof Android !== 'undefined' && Android.loadHistory) {
+                const saved = Android.loadHistory();
+                return saved ? JSON.parse(saved) : [];
+            }
+        } catch (e) {}
+        // 回退到 localStorage
+        try {
+            const saved = localStorage.getItem('symja_history');
+            return saved ? JSON.parse(saved) : [];
+        } catch (e) { return []; }
+    }
+
+    saveHistory() {
+        const json = JSON.stringify(this.history);
+        try {
+            if (typeof Android !== 'undefined' && Android.saveHistory) {
+                Android.saveHistory(json);
+            }
+        } catch (e) {}
+        try {
+            localStorage.setItem('symja_history', json);
+        } catch (e) {}
+    }
+
+    saveSession(name) {
+        try {
+            const session = {
+                history: this.history,
+                variables: Array.from(this.variables),
+                timestamp: new Date().toISOString()
+            };
+            localStorage.setItem(`symja_session_${name}`, JSON.stringify(session));
+            this.addOutput(`💾 会话已保存: ${name}`, 'help');
+        } catch (e) {
+            this.addOutput(`❌ 保存失败: ${e.message}`, 'error');
+        }
+        this.scrollToBottom();
+    }
+
+    addOutput(text, type, options = {}) {
+        const line = document.createElement('div');
+        line.className = 'terminal-line';
+
+        if (type === 'input') {
+            // 命令输入显示(带计数器)
+            const count = options.count !== undefined ? options.count : '';
+            line.innerHTML = `
+                <div class="command-line">
+                    <span class="command-prompt">[${count}]:</span>
+                    <span class="command-text">${this.escapeHtml(text)}</span>
+                </div>
+            `;
+        } else if (type === 'result') {
+            const isError = text.startsWith('错误:');
+            
+            // 检查是否需要 LaTeX 渲染
+            if (options.renderLatex && !isError) {
+                const count = options.count !== undefined ? options.count : '';
+                line.innerHTML = `
+                    <div class="latex-line">
+                        <div class="result-prompt">[${count}]:</div>
+                        <div class="latex-content">
+                            <div class="latex-rendered" id="latex-${Date.now()}"></div>
+                            <div class="latex-source" style="display: none;">${this.escapeHtml(text)}</div>
+                            <div class="latex-actions">
+                                <button class="latex-btn latex-toggle" onclick="symjaConsole.toggleLatexSource(this)">显示源码</button>
+                                <button class="latex-btn latex-copy" onclick="symjaConsole.copyLatex(this)">复制 LaTeX</button>
+                            </div>
+                        </div>
+                    </div>
+                `;
+                this.output.appendChild(line);
+                
+                // 渲染 LaTeX
+                const renderedDiv = line.querySelector('.latex-rendered');
+                this.renderLatex(text, renderedDiv);
+            } else {
+                const className = isError ? 'error-line' : 'result-line';
+                const count = options.count !== undefined ? options.count : '';
+                line.innerHTML = `
+                    <div class="${className}">
+                        <span class="result-prompt">[${count}]:</span>
+                        <span class="result-text">${this.escapeHtml(text)}</span>
+                    </div>
+                `;
+            }
+        } else if (type === 'help') {
+            line.innerHTML = `<div class="info-line">${this.escapeHtml(text)}</div>`;
+        } else if (type === 'error') {
+            line.innerHTML = `<div class="error-line">${this.escapeHtml(text)}</div>`;
+        } else if (type === 'plugin-info') {
+            // 插件的控制台输出(通过 Symja.print() 输出的内容)
+            line.innerHTML = `<div class="plugin-info-line">${this.escapeHtml(text)}</div>`;
+        } else if (type === 'plugin') {
+            // 插件的返回值(可运算结果)
+            line.innerHTML = `<div class="plugin-line">${this.escapeHtml(text)}</div>`;
+        } else if (type === 'mcp-input') {
+            const seq = options.seq || '';
+            line.innerHTML = `<div class="mcp-input-line">
+                <span class="mcp-badge">🌐 MCP</span>
+                <span class="mcp-prompt">[${seq}]:</span>
+                <span class="mcp-command-text">${this.escapeHtml(text)}</span>
+            </div>`;
+        } else if (type === 'mcp-latex') {
+            line.innerHTML = `<div class="latex-line">
+                <div class="latex-content">
+                    <div class="latex-rendered" id="latex-mcp-${Date.now()}"></div>
+                    <div class="latex-source" style="display:none">${this.escapeHtml(text)}</div>
+                    <div class="latex-actions">
+                        <button class="latex-btn latex-toggle" onclick="symjaConsole.toggleLatexSource(this)">显示源码</button>
+                        <button class="latex-btn latex-copy" onclick="symjaConsole.copyLatex(this)">复制 LaTeX</button>
+                    </div>
+                </div>
+            </div>`;
+            this.output.appendChild(line);
+            const renderedDiv = line.querySelector('.latex-rendered');
+            this.renderLatex(text, renderedDiv);
+        } else if (type === 'mcp-result') {
+            line.innerHTML = `<div class="mcp-result-line">
+                <span class="mcp-prompt"></span>
+                <span class="mcp-result-text">${this.escapeHtml(text)}</span>
+            </div>`;
+        }
+        
+        this.output.appendChild(line);
+    }
+
+    // LaTeX 渲染功能
+    renderLatex(latex, element) {
+        try {
+            // 等待 KaTeX 加载
+            if (typeof katex !== 'undefined') {
+                // 清理 LaTeX 字符串
+                let cleanLatex = latex.trim();
+                
+                // 如果是 Symja 的 TeXForm 输出,可能需要一些转换
+                if (cleanLatex.startsWith('TeXForm:')) {
+                    cleanLatex = cleanLatex.substring(8).trim();
+                }
+                
+                // 使用 KaTeX 渲染
+                katex.render(cleanLatex, element, {
+                    throwOnError: false,
+                    displayMode: true,
+                    output: 'html',
+                    trust: false,
+                    strict: false
+                });
+            } else {
+                // KaTeX 未加载,显示格式化的源码
+                element.style.fontFamily = 'var(--font-mono)';
+                element.style.fontSize = '14px';
+                element.style.padding = '12px';
+                element.style.background = '#F5F5F5';
+                element.style.borderRadius = '4px';
+                element.style.overflowX = 'auto';
+                element.textContent = latex;
+                console.warn('KaTeX not loaded, showing source code');
+            }
+        } catch (e) {
+            // 渲染失败,显示源码
+            element.style.fontFamily = 'var(--font-mono)';
+            element.style.fontSize = '14px';
+            element.style.padding = '12px';
+            element.style.background = '#F5F5F5';
+            element.style.borderRadius = '4px';
+            element.style.overflowX = 'auto';
+            element.textContent = latex;
+            console.error('LaTeX rendering error:', e);
+        }
+    }
+
+    // 切换显示 LaTeX 源码
+    toggleLatexSource(button) {
+        const parent = button.closest('.latex-line');
+        if (!parent) return;
+        
+        const source = parent.querySelector('.latex-source');
+        if (!source) return;
+        
+        if (source.style.display === 'none') {
+            source.style.display = 'block';
+            button.textContent = '隐藏源码';
+        } else {
+            source.style.display = 'none';
+            button.textContent = '显示源码';
+        }
+    }
+
+    // 复制 LaTeX 代码
+    copyLatex(button) {
+        const parent = button.closest('.latex-line');
+        if (!parent) return;
+        
+        const source = parent.querySelector('.latex-source');
+        if (!source) return;
+        
+        const text = source.textContent;
+        
+        // 创建临时文本区域
+        const textarea = document.createElement('textarea');
+        textarea.value = text;
+        textarea.style.position = 'fixed';
+        textarea.style.opacity = '0';
+        document.body.appendChild(textarea);
+        textarea.select();
+        
+        try {
+            document.execCommand('copy');
+            button.textContent = '已复制!';
+            setTimeout(() => {
+                button.textContent = '复制 LaTeX';
+            }, 2000);
+        } catch (e) {
+            console.error('Copy failed:', e);
+        }
+        
+        document.body.removeChild(textarea);
+    }
+
+    showHelp() {
+        const helpPage = document.getElementById('helpPage');
+        const helpContent = document.getElementById('helpContent');
+        
+        if (!helpPage || !helpContent) return;
+        
+        // 隐藏键盘
+        this.hideKeyboard();
+        
+        helpContent.innerHTML = `
+            <table class="help-table">
+                <thead>
+                    <tr>
+                        <th>说明</th>
+                        <th>命令</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr><td>求导</td><td>D[x^2, x]</td></tr>
+                    <tr><td>复合函数求导</td><td>D[Sin[x]*Cos[x], x]</td></tr>
+                    <tr><td>不定积分</td><td>Integrate[x^2, x]</td></tr>
+                    <tr><td>定积分</td><td>Integrate[x^2, {x, 0, 1}]</td></tr>
+                    <tr><td>求极限</td><td>Limit[Sin[x]/x, x -> 0]</td></tr>
+                    <tr><td>泰勒展开</td><td>Series[Exp[x], {x, 0, 5}]</td></tr>
+                    <tr><td>求和</td><td>Sum[i^2, {i, 1, 10}]</td></tr>
+                    <tr><td>求积(阶乘)</td><td>Product[i, {i, 1, 5}]</td></tr>
+                    
+                    <tr><td>展开</td><td>Expand[(x+1)^3]</td></tr>
+                    <tr><td>因式分解</td><td>Factor[x^2-4]</td></tr>
+                    <tr><td>化简</td><td>Simplify[Sin[x]^2+Cos[x]^2]</td></tr>
+                    <tr><td>通分</td><td>Together[(1/x)+(1/y)]</td></tr>
+                    <tr><td>部分分式</td><td>Apart[(x+1)/(x^2-1), x]</td></tr>
+                    <tr><td>约分</td><td>Cancel[(x^2-1)/(x-1)]</td></tr>
+                    <tr><td>合并同类项</td><td>Collect[x^2+3*x+2*x^2, x]</td></tr>
+                    
+                    <tr><td>解方程</td><td>Solve[x^2-5*x+6==0, x]</td></tr>
+                    <tr><td>方程组</td><td>Solve[{x+y==1, x-y==3}, {x,y}]</td></tr>
+                    <tr><td>数值求解</td><td>NSolve[x^3-2*x+1==0, x]</td></tr>
+                    <tr><td>求根</td><td>Roots[x^2-4==0, x]</td></tr>
+                    
+                    <tr><td>行列式</td><td>Det[{{1,2},{3,4}}]</td></tr>
+                    <tr><td>逆矩阵</td><td>Inverse[{{1,2},{3,4}}]</td></tr>
+                    <tr><td>转置</td><td>Transpose[{{1,2},{3,4}}]</td></tr>
+                    <tr><td>特征值</td><td>Eigenvalues[{{1,2},{3,4}}]</td></tr>
+                    <tr><td>特征向量</td><td>Eigenvectors[{{1,2},{3,4}}]</td></tr>
+                    <tr><td>矩阵秩</td><td>MatrixRank[{{1,2},{2,4}}]</td></tr>
+                    
+                    <tr><td>最大公约数</td><td>GCD[12, 18]</td></tr>
+                    <tr><td>最小公倍数</td><td>LCM[12, 18]</td></tr>
+                    <tr><td>质因数分解</td><td>FactorInteger[60]</td></tr>
+                    <tr><td>判断素数</td><td>PrimeQ[17]</td></tr>
+                    <tr><td>第 n 个素数</td><td>Prime[10]</td></tr>
+                    <tr><td>斐波那契数</td><td>Fibonacci[10]</td></tr>
+                    <tr><td>组合数 C(n,k)</td><td>Binomial[5, 2]</td></tr>
+                    
+                    <tr><td>高精度计算(50位)</td><td>N[Pi, 50]</td></tr>
+                    <tr><td>数值化</td><td>N[Sqrt[2]]</td></tr>
+                    <tr><td>四舍五入</td><td>Round[3.7]</td></tr>
+                    <tr><td>向下取整</td><td>Floor[3.7]</td></tr>
+                    <tr><td>向上取整</td><td>Ceiling[3.2]</td></tr>
+                    
+                    <tr><td>三角函数</td><td>Sin[x], Cos[x], Tan[x]</td></tr>
+                    <tr><td>反三角函数</td><td>ArcSin[x], ArcCos[x], ArcTan[x]</td></tr>
+                    <tr><td>双曲函数</td><td>Sinh[x], Cosh[x], Tanh[x]</td></tr>
+                    <tr><td>三角展开</td><td>TrigExpand[Sin[2*x]]</td></tr>
+                    <tr><td>三角化简</td><td>TrigReduce[Sin[x]^2]</td></tr>
+                    
+                    <tr><td>实部</td><td>Re[3+4*I]</td></tr>
+                    <tr><td>虚部</td><td>Im[3+4*I]</td></tr>
+                    <tr><td>模</td><td>Abs[3+4*I]</td></tr>
+                    <tr><td>辐角</td><td>Arg[3+4*I]</td></tr>
+                    <tr><td>共轭</td><td>Conjugate[3+4*I]</td></tr>
+                    
+                    <tr><td>生成表格</td><td>Table[i^2, {i, 1, 10}]</td></tr>
+                    <tr><td>生成数列</td><td>Range[1, 10]</td></tr>
+                    <tr><td>带步长的数列</td><td>Range[0, 1, 0.1]</td></tr>
+                    <tr><td>列表长度</td><td>Length[{1,2,3,4}]</td></tr>
+                    <tr><td>第一个元素</td><td>First[{1,2,3}]</td></tr>
+                    <tr><td>最后一个元素</td><td>Last[{1,2,3}]</td></tr>
+                    <tr><td>取前 n 个</td><td>Take[{1,2,3,4,5}, 3]</td></tr>
+                    <tr><td>删除前 n 个</td><td>Drop[{1,2,3,4,5}, 2]</td></tr>
+                    <tr><td>连接列表</td><td>Join[{1,2}, {3,4}]</td></tr>
+                    <tr><td>反转列表</td><td>Reverse[{1,2,3}]</td></tr>
+                    <tr><td>排序</td><td>Sort[{3,1,4,1,5}]</td></tr>
+                    
+                    <tr><td>替换规则</td><td>b+c /. b->1</td></tr>
+                    <tr><td>变量替换</td><td>x^2+2*x+1 /. x->3</td></tr>
+                    <tr><td>多重替换</td><td>expr /. {a->1, b->2}</td></tr>
+                    
+                    <tr><td>微分方程</td><td>DSolve[y'[x]==y[x], y[x], x]</td></tr>
+                    <tr><td>递推方程</td><td>RSolve[a[n]==2*a[n-1]+1, a[n], n]</td></tr>
+                    
+                    <tr><td>拉普拉斯变换</td><td>LaplaceTransform[Sin[t], t, s]</td></tr>
+                    <tr><td>逆拉普拉斯变换</td><td>InverseLaplaceTransform[1/(s^2+1), s, t]</td></tr>
+                    <tr><td>傅里叶变换</td><td>FourierTransform[Exp[-x^2], x, w]</td></tr>
+                    
+                    <tr><td>求最大值</td><td>Maximize[x^2-4*x+3, x]</td></tr>
+                    <tr><td>求最小值</td><td>Minimize[x^2-4*x+3, x]</td></tr>
+                    
+                    <tr><td>平均值</td><td>Mean[{1,2,3,4,5}]</td></tr>
+                    <tr><td>中位数</td><td>Median[{1,2,3,4,5}]</td></tr>
+                    <tr><td>方差</td><td>Variance[{1,2,3,4,5}]</td></tr>
+                    <tr><td>标准差</td><td>StandardDeviation[{1,2,3,4,5}]</td></tr>
+                    
+                    <tr><td>LaTeX 格式</td><td>TeXForm[x^2 + 2*x + 1]</td></tr>
+                    <tr><td>完整形式</td><td>FullForm[x+y]</td></tr>
+                    <tr><td>输入形式</td><td>InputForm[expr]</td></tr>
+                    
+                    <tr><td>显示帮助</td><td>help 或 ?</td></tr>
+                    <tr><td>清空输出</td><td>clear 或 cls</td></tr>
+                    <tr><td>命令历史</td><td>history</td></tr>
+                    <tr><td>清除变量</td><td>clear &lt;变量&gt;</td></tr>
+                    <tr><td>插件管理</td><td>plugins</td></tr>
+                    <tr><td>显示反馈邮箱</td><td>feedback</td></tr>
+                    
+                    <tr><td>圆周率 π</td><td>Pi</td></tr>
+                    <tr><td>自然常数 e</td><td>E</td></tr>
+                    <tr><td>虚数单位 i</td><td>I</td></tr>
+                    <tr><td>无穷大 ∞</td><td>Infinity</td></tr>
+                    <tr><td>布尔值</td><td>True, False</td></tr>
+                    <tr><td>上一次的输出结果</td><td>%</td></tr>
+                </tbody>
+            </table>
+        `;
+        
+        helpPage.classList.add('show');
+    }
+    
+    hideHelp() {
+        const helpPage = document.getElementById('helpPage');
+        if (helpPage) {
+            helpPage.classList.remove('show');
+        }
+    }
+    
+    showSettings() {
+        const panel = document.getElementById('settingsPanel');
+        if (!panel) return;
+        this.hideKeyboard();
+        document.getElementById('settingMcpEnabled').value = this.getMcpEnabled() ? '1' : '0';
+        document.getElementById('settingMcpPort').value = this.getSetting('mcpPort', '8734');
+        document.getElementById('settingFontSize').value = this.getSetting('fontSize', '14');
+        panel.classList.add('show');
+    }
+
+    hideSettings() {
+        document.getElementById('settingsPanel')?.classList.remove('show');
+    }
+
+    applySettings() {
+        const wasEnabled = this.getMcpEnabled();
+        const enabled = document.getElementById('settingMcpEnabled').value === '1';
+        const port = document.getElementById('settingMcpPort').value;
+        const fontSize = document.getElementById('settingFontSize').value;
+        this.saveSetting('mcpPort', port);
+        this.saveSetting('fontSize', fontSize);
+        this.setMcpEnabled(enabled);
+        document.querySelector('.terminal-output').style.fontSize = fontSize + 'px';
+        document.querySelector('.terminal-input').style.fontSize = fontSize + 'px';
+        this.hideSettings();
+        this.addOutput('⚙️ 设置已应用', 'help');
+        this.scrollToBottom();
+
+        // MCP 状态变更时更新轮询
+        if (enabled !== wasEnabled) {
+            if (enabled) {
+                this.startMcpPollingIfNeeded();
+            } else {
+                this.stopMcpPolling();
+            }
+        }
+    }
+
+    getMcpEnabled() {
+        try {
+            if (typeof Android !== 'undefined' && Android.getMcpEnabled !== undefined) {
+                return Android.getMcpEnabled();
+            }
+        } catch(e) {}
+        return this.getSetting('mcpEnabled', '1') === '1';
+    }
+
+    setMcpEnabled(val) {
+        this.saveSetting('mcpEnabled', val ? '1' : '0');
+        try {
+            if (typeof Android !== 'undefined' && Android.setMcpEnabled) {
+                Android.setMcpEnabled(val);
+            }
+        } catch(e) {}
+    }
+
+    startMcpPollingIfNeeded() {
+        if (!this.getMcpEnabled()) return;
+        if (!this.mcpPollTimer) {
+            this.mcpPollTimer = setInterval(() => this.pollMcpEvents(), 1000);
+        }
+    }
+
+    stopMcpPolling() {
+        if (this.mcpPollTimer) { clearInterval(this.mcpPollTimer); this.mcpPollTimer = null; }
+    }
+
+    getSetting(key, def) {
+        try { return localStorage.getItem('ijs_' + key) || def; } catch(e) { return def; }
+    }
+
+    saveSetting(key, val) {
+        try { localStorage.setItem('ijs_' + key, val); } catch(e) {}
+    }
+
+    loadSettings() {
+        const fs = this.getSetting('fontSize', '14');
+        try {
+            document.querySelector('.terminal-output').style.fontSize = fs + 'px';
+            document.querySelector('.terminal-input').style.fontSize = fs + 'px';
+        } catch(e) {}
+    }
+
+    clear() {
+        this.output.innerHTML = '';
+        this.showWelcome();
+        // 重置计数器
+        this.executionCount = 1;
+        this.saveExecutionCount();
+        this.updatePrompt();
+    }
+
+    reset() {
+        try {
+            const result = Android.reset();
+            this.variables.clear();
+            this.addOutput('🔄 ' + result, 'help');
+            this.scrollToBottom();
+            // 重置计数器
+            this.executionCount = 1;
+            this.saveExecutionCount();
+            this.updatePrompt();
+        } catch (e) {
+            this.addOutput('错误: 无法重置计算器', 'error');
+        }
+    }
+
+    updatePrompt() {
+        if (this.inputPrompt) {
+            this.inputPrompt.textContent = `[${this.executionCount}]:`;
+        }
+    }
+
+    loadExecutionCount() {
+        return 1;  // 每次打开应用从1开始
+    }
+
+    saveExecutionCount() {
+        // 不持久化编号,每次打开都重置
+    }
+
+    navigateHistory(direction) {
+        if (this.history.length === 0) {
+            return;
+        }
+
+        // 保存当前输入
+        if (this.historyIndex === this.history.length) {
+            this.tempInput = this.input.value;
+        }
+
+        this.historyIndex += direction;
+
+        if (this.historyIndex < 0) {
+            this.historyIndex = 0;
+        } else if (this.historyIndex >= this.history.length) {
+            this.historyIndex = this.history.length;
+            this.input.value = this.tempInput;
+            return;
+        }
+
+        this.input.value = this.history[this.historyIndex];
+        // 光标移到末尾
+        setTimeout(() => {
+            this.input.setSelectionRange(this.input.value.length, this.input.value.length);
+        }, 0);
+    }
+
+    // 自动补全功能
+    updateCompletionList() {
+        const value = this.input.value;
+        const cursorPos = this.input.selectionStart;
+        const textBeforeCursor = value.substring(0, cursorPos);
+        const match = textBeforeCursor.match(/[A-Za-z0-9_-]+$/);
+        
+        console.log('updateCompletionList:', { value, textBeforeCursor, match });
+        
+        if (match && match[0].length >= 2) {
+            const prefix = match[0];
+            console.log('Searching completions for prefix:', prefix);
+            
+            // 特殊命令列表
+            const specialCommands = ['help', 'clear', 'cls', 'history', 'plugins', 'mcp', 'feedback'];
+            
+            // 获取插件命令列表
+            const pluginCommands = this.plugins
+                .filter(p => p.command && p.command.length > 0)
+                .map(p => p.command);
+            
+            try {
+                const completionsJson = Android.getCompletions(prefix);
+                console.log('Raw completions JSON:', completionsJson);
+                
+                // 解析补全结果 - 可能是逗号分隔的字符串或 JSON 数组
+                let symjaCompletions = [];
+                if (completionsJson.startsWith('[')) {
+                    // JSON 数组格式
+                    symjaCompletions = JSON.parse(completionsJson);
+                } else {
+                    // 逗号分隔的字符串格式
+                    symjaCompletions = completionsJson.split(',').map(s => s.trim()).filter(s => s.length > 0);
+                }
+                
+                // 添加匹配的特殊命令
+                const lowerPrefix = prefix.toLowerCase();
+                const matchingCommands = specialCommands.filter(cmd => cmd.startsWith(lowerPrefix));
+                
+                // 添加匹配的插件命令
+                const matchingPluginCommands = pluginCommands.filter(cmd => cmd.toLowerCase().startsWith(lowerPrefix));
+                
+                // 合并并去重(插件命令优先显示)
+                this.completions = [...new Set([...matchingPluginCommands, ...matchingCommands, ...symjaCompletions])];
+                
+                console.log('Parsed completions:', this.completions);
+                
+                if (this.completions.length > 0) {
+                    this.showCompletionList();
+                } else {
+                    this.hideCompletionList();
+                }
+            } catch (e) {
+                console.error('Completion error:', e);
+                this.completions = [];
+                this.hideCompletionList();
+            }
+        } else {
+            this.completions = [];
+            this.hideCompletionList();
+        }
+    }
+
+    showCompletionList() {
+        console.log('showCompletionList called');
+        console.log('completionList element:', this.completionList);
+        console.log('completions:', this.completions);
+        
+        if (!this.completionList) {
+            console.error('completionList element not found');
+            return;
+        }
+        
+        if (this.completions.length === 0) {
+            console.log('No completions to show');
+            return;
+        }
+        
+        this.completionList.innerHTML = '';
+        
+        // 最多显示 8 个补全项
+        const itemsToShow = this.completions.slice(0, 8);
+        console.log('Items to show:', itemsToShow);
+        
+        itemsToShow.forEach(completion => {
+            const item = document.createElement('div');
+            item.className = 'completion-item';
+            
+            // 检查是否是插件命令
+            const plugin = this.plugins.find(p => p.command === completion);
+            if (plugin) {
+                // 插件命令,显示图标和名称
+                item.innerHTML = `<span style="margin-right: 8px;">${plugin.icon}</span>${completion} <span style="color: #999; font-size: 12px; margin-left: 8px;">(${plugin.name})</span>`;
+            } else {
+                // 普通命令或函数
+                item.textContent = completion;
+            }
+            
+            item.addEventListener('click', () => {
+                this.applyCompletion(completion);
+            });
+            this.completionList.appendChild(item);
+        });
+        
+        this.completionList.classList.add('show');
+        console.log('Completion list should now be visible');
+    }
+
+    hideCompletionList() {
+        if (this.completionList) {
+            this.completionList.classList.remove('show');
+        }
+    }
+
+    applyCompletion(completion) {
+        const value = this.input.value;
+        const cursorPos = this.input.selectionStart;
+        const textBeforeCursor = value.substring(0, cursorPos);
+        const textAfterCursor = value.substring(cursorPos);
+
+        const match = textBeforeCursor.match(/[A-Za-z0-9_-]+$/);
+        if (match) {
+            const prefix = match[0];
+            const beforePrefix = textBeforeCursor.substring(0, textBeforeCursor.length - prefix.length);
+            
+            // 检查是否是插件命令
+            const plugin = this.plugins.find(p => p.command === completion);
+            if (plugin && plugin.inputs && plugin.inputs.length > 0) {
+                // 插件命令,添加参数括号和提示
+                const paramHints = plugin.inputs.map(input => input.name).join(', ');
+                this.input.value = beforePrefix + completion + '[' + paramHints + ']' + textAfterCursor;
+                
+                // 将光标放在第一个参数位置
+                const newCursorPos = beforePrefix.length + completion.length + 1;
+                this.input.setSelectionRange(newCursorPos, newCursorPos + plugin.inputs[0].name.length);
+            } else {
+                // 普通命令或函数
+                this.input.value = beforePrefix + completion + textAfterCursor;
+                
+                const newCursorPos = beforePrefix.length + completion.length;
+                this.input.setSelectionRange(newCursorPos, newCursorPos);
+            }
+        }
+        
+        this.hideCompletionList();
+        this.input.focus();
+    }
+
+    handleAutoComplete() {
+        // 保留这个方法以防其他地方调用,但不再使用
+        this.updateCompletionList();
+    }
+
+    // 插件相关功能
+    loadPlugins() {
+        try {
+            const pluginsJson = Android.getPlugins();
+            console.log('Loaded plugins JSON:', pluginsJson);
+            this.plugins = JSON.parse(pluginsJson);
+            console.log('Parsed plugins count:', this.plugins.length);
+            console.log('Plugins:', this.plugins);
+        } catch (e) {
+            console.error('Failed to load plugins:', e);
+            this.plugins = [];
+        }
+    }
+
+    showPluginPanel() {
+        this.loadPlugins();
+        const panel = document.getElementById('pluginPanel');
+        
+        if (!panel) return;
+        
+        // 隐藏键盘
+        this.hideKeyboard();
+        
+        // 确保默认显示插件列表标签
+        const tabs = panel.querySelectorAll('.plugin-tab');
+        const contents = panel.querySelectorAll('.tab-content');
+        
+        tabs.forEach(tab => {
+            if (tab.dataset.tab === 'list') {
+                tab.classList.add('active');
+            } else {
+                tab.classList.remove('active');
+            }
+        });
+        
+        contents.forEach(content => {
+            if (content.dataset.content === 'list') {
+                content.classList.add('active');
+            } else {
+                content.classList.remove('active');
+            }
+        });
+        
+        // 更新插件下拉列表(用于分享和删除)
+        this.updatePluginSelects();
+        
+        // 使用统一的刷新方法
+        this.refreshPluginList();
+        
+        panel.classList.add('show');
+    }
+
+    hidePluginPanel() {
+        const panel = document.getElementById('pluginPanel');
+        if (panel) panel.classList.remove('show');
+    }
+
+    openPlugin(plugin) {
+        this.currentPlugin = plugin;
+        this.hidePluginPanel();
+
+        // Fullscreen mode — open the fullscreen panel
+        if (plugin.mode === 'fullscreen') {
+            this.openFullscreenPlugin(plugin);
+            return;
+        }
+
+        // Inline mode — open the standard dialog
+        const dialog = document.getElementById('pluginDialog');
+        const title = document.getElementById('pluginDialogTitle');
+        const desc = document.getElementById('pluginDialogDesc');
+        const inputs = document.getElementById('pluginInputs');
+        
+        if (!dialog || !title || !desc || !inputs) return;
+        
+        title.textContent = plugin.icon + ' ' + plugin.name;
+        desc.textContent = plugin.description;
+        
+        inputs.innerHTML = '';
+        plugin.inputs.forEach(input => {
+            const inputGroup = document.createElement('div');
+            inputGroup.className = 'input-group';
+            inputGroup.innerHTML = `
+                <label>${input.label}</label>
+                <input type="text" 
+                       name="${input.name}" 
+                       value="${input.defaultValue || ''}"
+                       placeholder="${input.placeholder || ''}">
+            `;
+            inputs.appendChild(inputGroup);
+        });
+        
+        dialog.classList.add('show');
+    }
+
+    hidePluginDialog() {
+        const dialog = document.getElementById('pluginDialog');
+        if (dialog) dialog.classList.remove('show');
+        this.currentPlugin = null;
+    }
+
+    openFullscreenPlugin(plugin) {
+        const panel = document.getElementById('fullscreenPanel');
+        document.getElementById('fsPluginTitle').textContent = plugin.icon + ' ' + plugin.name;
+        panel.classList.add('show');
+
+        // Run the plugin in fullscreen mode
+        const pluginOutputs = [];
+        const ijsAPI = this.createIJSAPI(pluginOutputs);
+        this._lastIjsApi = ijsAPI;  // 保存引用供 MCP __tap 调试使用
+
+        // Create a default inputs object from plugin defaults
+        const inputValues = {};
+        if (plugin.inputs) {
+            plugin.inputs.forEach(function(inp) {
+                inputValues[inp.name] = inp.defaultValue || '';
+            });
+        }
+
+        // Fullscreen plugins don't return values — they use IJS.canvas/IJS.input
+        const func = new Function('inputs', 'IJS', plugin.jsCode + '\ncalculate(inputs, IJS);');
+        try {
+            func(inputValues, ijsAPI);
+        } catch (e) {
+            console.error('Fullscreen plugin error:', e);
+            ijsAPI.ui.close();
+            this.addOutput('插件错误: ' + e.message, 'error');
+            this.scrollToBottom();
+        }
+    }
+
+    executeCurrentPlugin() {
+        if (!this.currentPlugin) return;
+        
+        const inputsDiv = document.getElementById('pluginInputs');
+        if (!inputsDiv) return;
+        
+        const inputElements = inputsDiv.querySelectorAll('input');
+        const inputValues = {};
+        
+        // 收集输入值并转换为 Symja 可运算类型
+        inputElements.forEach(input => {
+            inputValues[input.name] = this.convertInputToSymjaType(input.value);
+        });
+        
+        try {
+            // 用于存储插件的控制台输出
+            const pluginOutputs = [];
+            
+            // 创建 IJS API 对象
+            const ijsAPI = this.createIJSAPI(pluginOutputs);
+
+            // 执行插件代码
+            const func = new Function('inputs', 'IJS', this.currentPlugin.jsCode + '\nreturn calculate(inputs, IJS);');
+            const result = func(inputValues, ijsAPI);
+            
+            // 验证返回值是否为有效的可运算类型
+            if (typeof result !== 'string' || result.trim() === '') {
+                throw new Error('插件必须返回非空字符串类型的 Symja 表达式');
+            }
+            
+            // 验证返回值是否是有效的 Symja 表达式(尝试计算)
+            try {
+                Android.symjaEval(result);
+            } catch (e) {
+                throw new Error('插件返回值必须是有效的 Symja 表达式,当前返回: ' + result);
+            }
+            
+            // 显示插件名称
+            this.addOutput(`${this.currentPlugin.icon} ${this.currentPlugin.name}`, 'input');
+            
+            // 显示插件的控制台输出(如果有)
+            if (pluginOutputs.length > 0) {
+                pluginOutputs.forEach(output => {
+                    this.addOutput(output, 'plugin-info');
+                });
+            }
+            
+            // 显示插件的返回值(可运算结果)
+            this.addOutput(result, 'plugin');
+            
+            // 将插件结果保存到 Symja 引擎,使其可以通过 % 引用
+            try {
+                Android.evaluate(`Out = ${result}`);
+            } catch (e) {
+                console.log('Plugin result storage error:', e);
+            }
+            
+            this.scrollToBottom();
+            this.hidePluginDialog();
+        } catch (e) {
+            this.addOutput(`插件执行错误: ${e.message}`, 'error');
+        }
+    }
+
+    scrollToBottom() {
+        this.output.scrollTop = this.output.scrollHeight;
+    }
+
+    escapeHtml(text) {
+        const div = document.createElement('div');
+        div.textContent = text;
+        return div.innerHTML;
+    }
+
+    hideKeyboard() {
+        // 移除输入框焦点
+        if (this.input) {
+            this.input.blur();
+        }
+
+        // 调用 Android 接口隐藏键盘
+        try {
+            if (typeof Android !== 'undefined' && Android.hideKeyboard) {
+                Android.hideKeyboard();
+            }
+        } catch (e) {
+            console.log('hideKeyboard not available:', e);
+        }
+    }
+
+    pollMcpEvents() {
+        try {
+            if (typeof Android === 'undefined' || !Android.getMcpEvents) return;
+            const json = Android.getMcpEvents();
+            if (!json || json === '[]') return;
+            const events = JSON.parse(json);
+            events.forEach(ev => {
+                // 调试命令:MCP 触发打开全屏插件
+                if (ev.expression.startsWith('##OPEN_PLUGIN##')) {
+                    var pid = ev.expression.substring(16);
+                    var pl = this.plugins.find(function(p) { return p.id === pid || p.command === pid; });
+                    if (pl) this.openPlugin(pl);
+                    return;
+                }
+                // __eval 结果回传
+                if (ev.expression === '__eval_result') {
+                    try {
+                        var er = JSON.parse(ev.result);
+                        if (er.ok) {
+                            this.addOutput('__eval → ' + JSON.stringify(er.result), 'mcp-result');
+                        } else {
+                            this.addOutput('__eval error: ' + er.error, 'error');
+                        }
+                    } catch(e) {
+                        this.addOutput('__eval raw: ' + ev.result, 'mcp-result');
+                    }
+                    return;
+                }
+
+                // 调试命令:模拟画布点击 __tap x y (逻辑坐标)
+                if (ev.expression.startsWith('__tap ')) {
+                    var parts = ev.expression.split(/\s+/);
+                    var tx = parseFloat(parts[1]), ty = parseFloat(parts[2]);
+                    if (!isNaN(tx) && !isNaN(ty)) {
+                        // 将逻辑坐标转换为屏幕 clientX/Y
+                        var c = document.querySelector('#fullscreenContent canvas');
+                        if (c) {
+                            var r = c.getBoundingClientRect();
+                            var cx = r.left + (tx / (parseInt(c.style.width) || r.width)) * r.width;
+                            var cy = r.top + (ty / (parseInt(c.style.height) || r.height)) * r.height;
+                            // 通过 _fireTouch 直接触发插件回调
+                            try {
+                                // 获取当前插件的 IJS API 实例,调用 _fireTouch
+                                var apiInstance = symjaConsole._lastIjsApi;
+                                if (apiInstance && apiInstance.input && apiInstance.input._fireTouch) {
+                                    apiInstance.input._fireTouch(cx, cy);
+                                }
+                            } catch(e) { console.log('_fireTouch error:', e); }
+                        }
+                    }
+                    return;
+                }
+                this.mcpEventCount++;
+                this.addToHistory(ev.expression);
+                this.addOutput(ev.expression, 'mcp-input', { seq: this.mcpEventCount });
+                // 检测 LaTeX 渲染
+                if (ev.result && ev.result.startsWith('##TEXFORM##')) {
+                    const latex = ev.result.substring(11);
+                    this.addOutput(latex, 'mcp-latex', { renderLatex: true });
+                }
+                // 检测绘图结果 → Canvas 渲染
+                else if (ev.result && ev.result.startsWith('{"type":"plot"')) {
+                    this.addPlotOutput(ev.result, this.mcpEventCount);
+                } else {
+                    this.addOutput(ev.result, 'mcp-result', { seq: this.mcpEventCount });
+                }
+            });
+            this.scrollToBottom();
+        } catch (e) {
+            // Ignore polling errors
+        }
+    }
+}
+
+// 初始化控制台
+let symjaConsole;
+document.addEventListener('DOMContentLoaded', () => {
+    symjaConsole = new SymjaConsole();
+});
+
+// ── Canvas 绘图渲染器 ────────────────────────────────────────
+class PlotRenderer {
+    constructor() {
+        this.canvas = null;
+        this.ctx = null;
+        this.data = null;
+        this.transform = { ox: 0, oy: 0, sx: 1, sy: 1 };
+        this.bounds = { xMin: 0, xMax: 1, yMin: 0, yMax: 1 };
+        this.dpr = window.devicePixelRatio || 1;
+        this.colors = [
+            [0.369, 0.507, 0.710], [0.851, 0.208, 0.125],
+            [0.267, 0.612, 0.329], [0.737, 0.349, 0.800],
+            [1.0, 0.608, 0.0]
+        ];
+    }
+
+    render(canvas, data) {
+        this.canvas = canvas;
+        this.ctx = canvas.getContext('2d');
+        this.data = data;
+
+        // 计算全局范围
+        this.bounds = this.computeBounds(data);
+        const padX = (this.bounds.xMax - this.bounds.xMin) * 0.05 || 0.5;
+        const padY = (this.bounds.yMax - this.bounds.yMin) * 0.05 || 0.5;
+        this.bounds.xMin -= padX;
+        this.bounds.xMax += padX;
+        this.bounds.yMin -= padY;
+        this.bounds.yMax += padY;
+
+        // 强制等比例:确保圆不变椭圆
+        const m = 60, cw = canvas.width - 2 * m, ch = canvas.height - 2 * m;
+        const xr = this.bounds.xMax - this.bounds.xMin;
+        const yr = this.bounds.yMax - this.bounds.yMin;
+        if (xr > 0 && yr > 0) {
+            if (xr / cw > yr / ch) {
+                const ny = xr * ch / cw;
+                const my = (this.bounds.yMin + this.bounds.yMax) / 2;
+                this.bounds.yMin = my - ny / 2;
+                this.bounds.yMax = my + ny / 2;
+            } else {
+                const nx = yr * cw / ch;
+                const mx = (this.bounds.xMin + this.bounds.xMax) / 2;
+                this.bounds.xMin = mx - nx / 2;
+                this.bounds.xMax = mx + nx / 2;
+            }
+        }
+
+        this.transform = { ox: 0, oy: 0, sx: 1, sy: 1 };
+        this.draw();
+
+        // 手势绑定
+        this.bindGestures();
+    }
+
+    computeBounds(data) {
+        let xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity;
+        if (!data.elements) return { xMin: -1, xMax: 1, yMin: -1, yMax: 1 };
+        for (const el of data.elements) {
+            if (!el.points) continue;
+            for (const pt of el.points) {
+                xMin = Math.min(xMin, pt[0]);
+                xMax = Math.max(xMax, pt[0]);
+                yMin = Math.min(yMin, pt[1]);
+                yMax = Math.max(yMax, pt[1]);
+            }
+        }
+        if (!isFinite(xMin)) return { xMin: -1, xMax: 1, yMin: -1, yMax: 1 };
+        return { xMin, xMax, yMin, yMax };
+    }
+
+    // ── 坐标变换 ──────────────────────────────────────
+    toCanvas(px, py) {
+        const w = this.canvas.width, h = this.canvas.height;
+        const m = 60; // 边距
+        const cw = w - 2 * m, ch = h - 2 * m;
+        const t = this.transform;
+        const b = this.bounds;
+        const xRange = b.xMax - b.xMin, yRange = b.yMax - b.yMin;
+        return {
+            x: m + (px - b.xMin - t.ox * xRange) / (xRange * t.sx) * cw,
+            y: h - m - (py - b.yMin - t.oy * yRange) / (yRange * t.sy) * ch
+        };
+    }
+
+    // ── 主绘制 ────────────────────────────────────────
+    draw() {
+        const ctx = this.ctx;
+        const w = this.canvas.width, h = this.canvas.height;
+        const m = 60;
+        const cw = w - 2 * m, ch = h - 2 * m;
+        const t = this.transform;
+        const b = this.bounds;
+
+        // 背景
+        ctx.fillStyle = '#FFFFFF';
+        ctx.fillRect(0, 0, w, h);
+
+        // 网格
+        this.drawGrid(m, cw, ch);
+
+        // 坐标轴
+        this.drawAxes(m, cw, ch);
+
+        // 数据曲线
+        if (this.data.elements) {
+            const useColors = this.colors;
+            this.data.elements.forEach((el, idx) => {
+                if (!el.points || el.points.length === 0) return;
+                const color = el.color || useColors[idx % useColors.length];
+                const rgb = `rgb(${Math.round(color[0]*255)},${Math.round(color[1]*255)},${Math.round(color[2]*255)})`;
+
+                if (el.type === 'line' || (el.type === 'point' && this.data.options && this.data.options.joined)) {
+                    ctx.strokeStyle = rgb;
+                    ctx.lineWidth = 3 * this.dpr;
+                    ctx.beginPath();
+                    for (let i = 0; i < el.points.length; i++) {
+                        const pt = this.toCanvas(el.points[i][0], el.points[i][1]);
+                        if (i === 0) ctx.moveTo(pt.x, pt.y);
+                        else ctx.lineTo(pt.x, pt.y);
+                    }
+                    ctx.stroke();
+                }
+
+                if (el.type === 'point') {
+                    ctx.fillStyle = rgb;
+                    for (const pt of el.points) {
+                        const p = this.toCanvas(pt[0], pt[1]);
+                        ctx.beginPath();
+                        ctx.arc(p.x, p.y, 3 * this.dpr, 0, Math.PI * 2);
+                        ctx.fill();
+                    }
+                }
+            });
+        }
+    }
+
+    drawGrid(m, cw, ch) {
+        const ctx = this.ctx;
+        const b = this.bounds;
+        const t = this.transform;
+        const xRange = b.xMax - b.xMin, yRange = b.yMax - b.yMin;
+
+        ctx.strokeStyle = '#E8EDF2';
+        ctx.lineWidth = Math.round(1 * this.dpr);
+        ctx.fillStyle = '#8B95A5';
+        ctx.font = '11px -apple-system, sans-serif';
+        ctx.textAlign = 'center';
+
+        const xStep = this.niceStep(xRange, 5);
+        const yStep = this.niceStep(yRange, 5);
+
+        // X 网格 + 标签
+        let xStart = Math.floor((b.xMin + t.ox * xRange) / xStep) * xStep;
+        for (let x = xStart; x <= b.xMin + t.ox * xRange + xRange / t.sx + xStep; x += xStep) {
+            const pt = this.toCanvas(x, 0);
+            if (pt.x >= m && pt.x <= m + cw) {
+                ctx.beginPath();
+                ctx.moveTo(pt.x, m);
+                ctx.lineTo(pt.x, m + ch);
+                ctx.stroke();
+                ctx.fillText(this.fmtNum(x), pt.x, m + ch + 16);
+            }
+        }
+
+        // Y 网格 + 标签
+        ctx.textAlign = 'right';
+        let yStart = Math.floor((b.yMin + t.oy * yRange) / yStep) * yStep;
+        for (let y = yStart; y <= b.yMin + t.oy * yRange + yRange / t.sy + yStep; y += yStep) {
+            const pt = this.toCanvas(0, y);
+            if (pt.y >= m && pt.y <= m + ch) {
+                ctx.beginPath();
+                ctx.moveTo(m, pt.y);
+                ctx.lineTo(m + cw, pt.y);
+                ctx.stroke();
+                ctx.fillText(this.fmtNum(y), m - 8, pt.y + 4);
+            }
+        }
+    }
+
+    drawAxes(m, cw, ch) {
+        const ctx = this.ctx;
+        const b = this.bounds;
+        const t = this.transform;
+        const origin = this.toCanvas(0, 0);
+
+        ctx.strokeStyle = '#1976D2';
+        ctx.lineWidth = Math.round(2 * this.dpr);
+        ctx.fillStyle = '#1976D2';
+        ctx.font = 'bold 11px -apple-system, sans-serif';
+
+        // X 轴
+        if (origin.y >= m && origin.y <= m + ch) {
+            ctx.beginPath();
+            ctx.moveTo(m, origin.y);
+            ctx.lineTo(m + cw, origin.y);
+            ctx.stroke();
+            // 箭头
+            ctx.beginPath();
+            ctx.moveTo(m + cw, origin.y);
+            ctx.lineTo(m + cw - 8, origin.y - 4);
+            ctx.lineTo(m + cw - 8, origin.y + 4);
+            ctx.fill();
+        }
+
+        // Y 轴
+        if (origin.x >= m && origin.x <= m + cw) {
+            ctx.beginPath();
+            ctx.moveTo(origin.x, m + ch);
+            ctx.lineTo(origin.x, m);
+            ctx.stroke();
+            // 箭头
+            ctx.beginPath();
+            ctx.moveTo(origin.x, m);
+            ctx.lineTo(origin.x - 4, m + 8);
+            ctx.lineTo(origin.x + 4, m + 8);
+            ctx.fill();
+        }
+
+        // 原点标签
+        if (origin.x >= m && origin.x <= m + cw && origin.y >= m && origin.y <= m + ch) {
+            ctx.textAlign = 'right';
+            ctx.fillText('O', origin.x - 6, origin.y + 14);
+        }
+    }
+
+    niceStep(range, target) {
+        const rough = range / target;
+        const exp = Math.pow(10, Math.floor(Math.log10(rough)));
+        const mant = rough / exp;
+        let nice = 1;
+        if (mant <= 1.5) nice = 1;
+        else if (mant <= 3.5) nice = 2;
+        else if (mant <= 7.5) nice = 5;
+        else nice = 10;
+        return nice * exp;
+    }
+
+    fmtNum(n) {
+        if (Math.abs(n) < 1e-10) return '0';
+        if (Math.abs(n) < 0.001 || Math.abs(n) >= 1e6) return n.toExponential(2);
+        if (Number.isInteger(n)) return n.toString();
+        const s = n.toFixed(4);
+        return s.replace(/0+$/, '').replace(/\.$/, '');
+    }
+
+    // ── 手势 ──────────────────────────────────────────
+    bindGestures() {
+        const canvas = this.canvas;
+        // 移除旧监听器防止泄露
+        if (this._touchHandlers) {
+            canvas.removeEventListener('touchstart', this._touchHandlers.start);
+            canvas.removeEventListener('touchmove', this._touchHandlers.move);
+            canvas.removeEventListener('touchend', this._touchHandlers.end);
+        }
+        let lastDist = 0, lastX = 0, lastY = 0, lastTap = 0, initialPinch = false;
+        const self = this;
+
+        const onStart = (e) => {
+            if (e.touches.length >= 2) {
+                const dx = e.touches[0].clientX - e.touches[1].clientX;
+                const dy = e.touches[0].clientY - e.touches[1].clientY;
+                lastDist = Math.sqrt(dx*dx + dy*dy);
+                initialPinch = true;
+            }
+            if (e.touches.length === 1 && !initialPinch) {
+                lastX = e.touches[0].clientX;
+                lastY = e.touches[0].clientY;
+            }
+        };
+        const onMove = (e) => {
+            if (e.touches.length >= 2 && initialPinch && lastDist > 0) {
+                const dx = e.touches[0].clientX - e.touches[1].clientX;
+                const dy = e.touches[0].clientY - e.touches[1].clientY;
+                const dist = Math.sqrt(dx*dx + dy*dy);
+                const scale = dist / lastDist;
+                if (scale > 0.3 && scale < 3) {
+                    self.transform.sx = Math.max(0.1, Math.min(20, self.transform.sx / scale));
+                    self.transform.sy = Math.max(0.1, Math.min(20, self.transform.sy / scale));
+                }
+                lastDist = dist;
+                self.draw();
+                e.preventDefault();
+            } else if (e.touches.length === 1 && !initialPinch) {
+                const dx = (e.touches[0].clientX - lastX) / canvas.clientWidth;
+                const dy = (e.touches[0].clientY - lastY) / canvas.clientHeight;
+                self.transform.ox -= dx / self.transform.sx;
+                self.transform.oy += dy / self.transform.sy;
+                lastX = e.touches[0].clientX;
+                lastY = e.touches[0].clientY;
+                self.draw();
+                e.preventDefault();
+            }
+        };
+        const onEnd = (e) => {
+            if (e.touches.length === 0) {
+                const now = Date.now();
+                if (initialPinch) {
+                    initialPinch = false;
+                } else if (now - lastTap < 350) {
+                    self.transform = { ox: 0, oy: 0, sx: 1, sy: 1 };
+                    self.draw();
+                }
+                lastTap = now;
+            }
+        };
+
+        canvas.addEventListener('touchstart', onStart, { passive: true });
+        canvas.addEventListener('touchmove', onMove, { passive: false });
+        canvas.addEventListener('touchend', onEnd, { passive: true });
+        this._touchHandlers = { start: onStart, move: onMove, end: onEnd };
+    }
+}

+ 8 - 4
app/src/main/java/com/nonvioce/ijs/McpServer.java

@@ -680,14 +680,16 @@ public class McpServer extends NanoHTTPD {
                 for (String line : consoleOutput) {
                     sb.append(line).append("\n");
                 }
-                sb.append("---\n");
             }
 
             if (result != null && result != Context.getUndefinedValue()) {
                 String exprStr = Context.toString(result);
-                // Evaluate the returned expression through Symja (same as WebView console)
-                String evaluated = symjaEngine.evaluate(exprStr);
-                sb.append(evaluated);
+                // Skip no-op return values (Null)
+                if (!"Null".equals(exprStr) && !"null".equals(exprStr)) {
+                    String evaluated = symjaEngine.evaluate(exprStr);
+                    if (sb.length() > 0 && !evaluated.isEmpty()) sb.append("\n");
+                    sb.append(evaluated);
+                }
             }
 
             String output = sb.toString().trim();
@@ -740,9 +742,11 @@ public class McpServer extends NanoHTTPD {
         public String sum(String expr, String var, String start, String end) { return symjaEngine.sum(expr, var, start, end); }
         public String product(String expr, String var, String start, String end) { return symjaEngine.product(expr, var, start, end); }
         public String limit(String expr, String var, String point) { return symjaEngine.limit(expr, var, point, ""); }
+        public String series(String expr, String var, String point, int order) { return symjaEngine.series(expr, var, point, order); }
         public String trigSimplify(String expr)              { return symjaEngine.trigSimplify(expr); }
         public String apart(String expr, String var)         { return symjaEngine.apart(expr, var); }
         public String together(String expr)                  { return symjaEngine.together(expr); }
+        public String renderLatex(String latex)              { return "LaTeX: " + latex; }
 
         // Matrix operations exposed as nested object
         public final MatrixOps matrix = new MatrixOps();