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

feat: plugin system v2 — fullscreen mode, IJS API, JS libs, io/audio

Plugin schema:
- New 'mode' field: 'inline' (dialog, default) or 'fullscreen' (interactive panel)
- Fullscreen panel HTML/CSS/JS with close+cleanup

IJS API (replaces Symja):
- IJS.Symja — math engine (eval, solve, integrate, plot, matrix, etc.)
- IJS.canvas — 2D drawing (create, fill, clear, loop, stop)
- IJS.input — touch events (onTouchStart/Move/End)
- IJS.io — file I/O in app data dir (read/write/list/delete)
- IJS.audio — sound synthesis (beep, playNote, playMelody)
- IJS.ui — panel control (close, setBackground)

JS Libraries bundled in assets/libs/:
- lodash 4.17.21 (72KB)
- seedrandom 3.0.5 (1.6KB)
- Matter.js 0.19.0 (79KB) — 2D physics engine

MCP:
- api://plugin resource — complete API reference for client discovery
- execute_plugin tool description mentions available APIs
- Java-side ioRead/ioWrite/ioList/ioDelete bridge methods

Demo game: prime_snake (素数贪吃蛇) — eat primes, avoid composites

All 14 default plugins updated to new IJS.Symja API

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

+ 42 - 0
app/src/main/assets/console.css

@@ -1604,3 +1604,45 @@ body {
     color: #9E9E9E;
     line-height: 1.6;
 }
+
+/* ── 全屏插件面板 ───────────────────────────────── */
+.fullscreen-panel {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100vw;
+    height: 100vh;
+    background: #FFFFFF;
+    z-index: 4000;
+    display: none;
+    flex-direction: column;
+}
+.fullscreen-panel.show {
+    display: flex;
+}
+.fullscreen-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 10px 16px;
+    background: var(--primary-color);
+    color: #FFFFFF;
+    min-height: 48px;
+}
+.fullscreen-title {
+    font-size: 16px;
+    font-weight: 500;
+}
+.fullscreen-content {
+    flex: 1;
+    overflow: hidden;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: #F5F5F5;
+}
+.fullscreen-content canvas {
+    display: block;
+    background: #FFFFFF;
+    box-shadow: 0 2px 16px rgba(0,0,0,0.12);
+}

+ 14 - 0
app/src/main/assets/console.html

@@ -8,6 +8,11 @@
     <!-- KaTeX for LaTeX rendering (本地) -->
     <link rel="stylesheet" href="katex/katex.min.css">
     <script defer src="katex/katex.min.js"></script>
+
+    <!-- 内置 JS 库 (插件可用) -->
+    <script src="libs/lodash.min.js"></script>
+    <script src="libs/seedrandom.min.js"></script>
+    <script src="libs/matter.min.js"></script>
     
     <link rel="stylesheet" href="console.css">
 </head>
@@ -327,6 +332,15 @@
         </div>
     </div>
 
+    <!-- 全屏插件面板 -->
+    <div id="fullscreenPanel" class="fullscreen-panel">
+        <div class="fullscreen-header">
+            <span id="fsPluginTitle" class="fullscreen-title"></span>
+            <button id="closeFullscreen" class="close-btn">✕</button>
+        </div>
+        <div id="fullscreenContent" class="fullscreen-content"></div>
+    </div>
+
     <!-- 设置面板 -->
     <div id="settingsPanel" class="settings-panel">
         <div class="settings-panel-header">

+ 249 - 209
app/src/main/assets/console.js

@@ -84,6 +84,15 @@ class SymjaConsole {
             }
         });
 
+        // 全屏面板关闭
+        document.getElementById('closeFullscreen')?.addEventListener('click', () => {
+            const fs = document.getElementById('fullscreenPanel');
+            fs.classList.remove('show');
+            // 停止动画循环
+            const api = this.createIJSAPI([]);
+            if (api) api.canvas.stop();
+        });
+
         // 插件面板事件
         document.getElementById('closePluginPanel')?.addEventListener('click', () => {
             this.hidePluginPanel();
@@ -738,13 +747,10 @@ class SymjaConsole {
             
             // 用于存储插件的控制台输出
             const pluginOutputs = [];
-            
-            // 创建 Symja API 对象
-            const symjaAPI = this.createSymjaAPI(pluginOutputs);
-            
-            // 执行插件代码
-            const func = new Function('inputs', 'Symja', plugin.jsCode + '\nreturn calculate(inputs, Symja);');
-            const result = func(inputValues, symjaAPI);
+
+            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);
             
@@ -847,222 +853,220 @@ class SymjaConsole {
         }
     }
     
-    createSymjaAPI(pluginOutputs) {
+    createIJSAPI(pluginOutputs) {
+        const self = this;
         return {
-            // 控制台输出 API
-            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;
-                }
+            // ── 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'; }
             },
-            
-            // 矩阵运算
-            matrix: {
-                det: function(matrix) {
-                    try {
-                        return Android.matrixOp('Det', matrix);
-                    } catch (e) {
-                        return '错误: ' + e.message;
+
+            // ── IJS.canvas 绘图 ────────────────────────────
+            canvas: {
+                _ctx: null,
+                _canvas: null,
+                _frameId: null,
+                create: function(width, height) {
+                    const panel = document.getElementById('fullscreenContent');
+                    if (!panel) return null;
+                    panel.innerHTML = '';
+                    const c = document.createElement('canvas');
+                    c.width = width || 500;
+                    c.height = height || 500;
+                    this._canvas = c;
+                    this._ctx = c.getContext('2d');
+                    panel.appendChild(c);
+                    return this._ctx;
+                },
+                getContext: function() { return this._ctx; },
+                getSize: function() {
+                    return this._canvas ? { width: this._canvas.width, height: this._canvas.height } : { width: 0, height: 0 };
+                },
+                clear: function() {
+                    if (this._ctx && this._canvas) {
+                        this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
                     }
                 },
-                inverse: function(matrix) {
-                    try {
-                        return Android.matrixOp('Inverse', matrix);
-                    } catch (e) {
-                        return '错误: ' + e.message;
+                fill: function(color) {
+                    if (this._ctx && this._canvas) {
+                        this._ctx.fillStyle = color || '#FFFFFF';
+                        this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
                     }
                 },
-                transpose: function(matrix) {
-                    try {
-                        return Android.matrixOp('Transpose', matrix);
-                    } catch (e) {
-                        return '错误: ' + e.message;
+                size: function(w, h) {
+                    if (this._canvas) {
+                        this._canvas.width = w;
+                        this._canvas.height = h;
                     }
                 },
-                eigenvalues: function(matrix) {
-                    try {
-                        return Android.matrixOp('Eigenvalues', matrix);
-                    } catch (e) {
-                        return '错误: ' + e.message;
+                // 动画循环
+                loop: function(callback) {
+                    const selfCanvas = this;
+                    function frame() {
+                        selfCanvas._frameId = requestAnimationFrame(frame);
+                        callback();
                     }
+                    this.stop();
+                    selfCanvas._frameId = requestAnimationFrame(frame);
                 },
-                rank: function(matrix) {
-                    try {
-                        return Android.matrixOp('MatrixRank', matrix);
-                    } catch (e) {
-                        return '错误: ' + e.message;
+                stop: function() {
+                    if (this._frameId) {
+                        cancelAnimationFrame(this._frameId);
+                        this._frameId = null;
                     }
                 }
             },
-            
-            // 数论
-            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;
+
+            // ── 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(); },
+                _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);
+                    });
                 }
             },
-            binomial: function(n, k) {
-                try {
-                    return Android.binomial(String(n), String(k));
-                } catch (e) {
-                    return '错误: ' + e.message;
+
+            // ── 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; }
                 }
             },
-            
-            // 数值计算
-            numeric: function(expr, precision) {
-                try {
-                    return Android.numericEval(expr, precision || 16);
-                } catch (e) {
-                    return '错误: ' + e.message;
+
+            // ── 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) {
+                    // 暂不支持,留待扩展
                 }
             },
-            
-            // 绘图
-            plot: function(func, variable, xMin, xMax) {
-                try {
-                    const r = Android.evaluatePlot('Plot[' + func + ', {' + variable + ', ' + xMin + ', ' + xMax + '}]');
-                    return r;
-                } catch (e) { return 'Error: ' + e.message; }
-            },
-            parametricPlot: function(xf, yf, t, tMin, tMax) {
-                try {
-                    const r = Android.evaluatePlot('ParametricPlot[{' + xf + ', ' + yf + '}, {' + t + ', ' + tMin + ', ' + tMax + '}]');
-                    return r;
-                } catch (e) { return 'Error: ' + e.message; }
-            },
-            listPlot: function(points) {
-                try {
-                    const r = Android.evaluatePlot('ListPlot[' + points + ']');
-                    return r;
-                } catch (e) { return 'Error: ' + e.message; }
-            },
 
-            // LaTeX 转换和渲染
-            toTeX: function(expr) {
-                try {
-                    return Android.toTeX(expr);
-                } catch (e) {
-                    return '错误: ' + e.message;
+            // ── 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;
                 }
-            },
-            
-            // 渲染 LaTeX(供插件使用)
-            renderLatex: (latex) => {
-                symjaConsole.addOutput(latex, 'result', { renderLatex: true });
-                return '已渲染 LaTeX';
             }
         };
     }
@@ -1910,7 +1914,14 @@ class SymjaConsole {
     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');
@@ -1944,6 +1955,35 @@ class SymjaConsole {
         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);
+
+        // 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;
         
@@ -1962,12 +2002,12 @@ class SymjaConsole {
             // 用于存储插件的控制台输出
             const pluginOutputs = [];
             
-            // 创建 Symja API 对象供插件使用
-            const symjaAPI = this.createSymjaAPI(pluginOutputs);
-            
-            // 执行插件代码,传入 inputs 和 Symja API
-            const func = new Function('inputs', 'Symja', this.currentPlugin.jsCode + '\nreturn calculate(inputs, Symja);');
-            const result = func(inputValues, symjaAPI);
+            // 创建 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() === '') {

Разница между файлами не показана из-за своего большого размера
+ 15 - 0
app/src/main/assets/libs/lodash.min.js


Разница между файлами не показана из-за своего большого размера
+ 5 - 0
app/src/main/assets/libs/matter.min.js


+ 1 - 0
app/src/main/assets/libs/seedrandom.min.js

@@ -0,0 +1 @@
+!function(f,a,c){var s,l=256,p="random",d=c.pow(l,6),g=c.pow(2,52),y=2*g,h=l-1;function n(n,t,r){function e(){for(var n=u.g(6),t=d,r=0;n<g;)n=(n+r)*l,t*=l,r=u.g(1);for(;y<=n;)n/=2,t/=2,r>>>=1;return(n+r)/t}var o=[],i=j(function n(t,r){var e,o=[],i=typeof t;if(r&&"object"==i)for(e in t)try{o.push(n(t[e],r-1))}catch(n){}return o.length?o:"string"==i?t:t+"\0"}((t=1==t?{entropy:!0}:t||{}).entropy?[n,S(a)]:null==n?function(){try{var n;return s&&(n=s.randomBytes)?n=n(l):(n=new Uint8Array(l),(f.crypto||f.msCrypto).getRandomValues(n)),S(n)}catch(n){var t=f.navigator,r=t&&t.plugins;return[+new Date,f,r,f.screen,S(a)]}}():n,3),o),u=new m(o);return e.int32=function(){return 0|u.g(4)},e.quick=function(){return u.g(4)/4294967296},e.double=e,j(S(u.S),a),(t.pass||r||function(n,t,r,e){return e&&(e.S&&v(e,u),n.state=function(){return v(u,{})}),r?(c[p]=n,t):n})(e,i,"global"in t?t.global:this==c,t.state)}function m(n){var t,r=n.length,u=this,e=0,o=u.i=u.j=0,i=u.S=[];for(r||(n=[r++]);e<l;)i[e]=e++;for(e=0;e<l;e++)i[e]=i[o=h&o+n[e%r]+(t=i[e])],i[o]=t;(u.g=function(n){for(var t,r=0,e=u.i,o=u.j,i=u.S;n--;)t=i[e=h&e+1],r=r*l+i[h&(i[e]=i[o=h&o+t])+(i[o]=t)];return u.i=e,u.j=o,r})(l)}function v(n,t){return t.i=n.i,t.j=n.j,t.S=n.S.slice(),t}function j(n,t){for(var r,e=n+"",o=0;o<e.length;)t[h&o]=h&(r^=19*t[h&o])+e.charCodeAt(o++);return S(t)}function S(n){return String.fromCharCode.apply(0,n)}if(j(c.random(),a),"object"==typeof module&&module.exports){module.exports=n;try{s=require("crypto")}catch(n){}}else"function"==typeof define&&define.amd?define(function(){return n}):c["seed"+p]=n}("undefined"!=typeof self?self:this,[],Math);

+ 1 - 1
app/src/main/assets/plugins/binomial_distribution.json

@@ -8,5 +8,5 @@
     {"name": "n", "label": "试验次数n", "defaultValue": "5"},
     {"name": "p", "label": "成功概率p (分数或小数)", "defaultValue": "1/2"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var n = parseInt(inputs.n), p = inputs.p;\n  var EX = Symja.eval(n + '*(' + p + ')');\n  var q = Symja.eval('1-(' + p + ')');\n  var DX = Symja.eval(n + '*(' + p + ')*(' + q + ')');\n  Symja.print('X ~ B(' + n + ', ' + p + ')');\n  Symja.print('E(X) = ' + EX + ' = ' + Symja.eval('N[' + EX + ',6]'));\n  Symja.print('D(X) = ' + DX + ' = ' + Symja.eval('N[' + DX + ',6]'));\n  Symja.print('');\n  Symja.print('分布列 P(X=k):');\n  for (var k = 0; k <= n; k++) {\n    var comb = Symja.eval('Binomial[' + n + ',' + k + ']');\n    var pk = Symja.eval(comb + '*(' + p + ')^' + k + '*(' + q + ')^(' + n + '-' + k + ')');\n    var pkNum = Symja.eval('N[' + pk + ',6]');\n    Symja.print('  k=' + k + ': ' + pk + ' ≈ ' + pkNum);\n  }\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var n = parseInt(inputs.n), p = inputs.p;\n  var EX = IJS.Symja.eval(n + '*(' + p + ')');\n  var q = IJS.Symja.eval('1-(' + p + ')');\n  var DX = IJS.Symja.eval(n + '*(' + p + ')*(' + q + ')');\n  IJS.Symja.print('X ~ B(' + n + ', ' + p + ')');\n  IJS.Symja.print('E(X) = ' + EX + ' = ' + IJS.Symja.eval('N[' + EX + ',6]'));\n  IJS.Symja.print('D(X) = ' + DX + ' = ' + IJS.Symja.eval('N[' + DX + ',6]'));\n  IJS.Symja.print('');\n  IJS.Symja.print('分布列 P(X=k):');\n  for (var k = 0; k <= n; k++) {\n    var comb = IJS.Symja.eval('Binomial[' + n + ',' + k + ']');\n    var pk = IJS.Symja.eval(comb + '*(' + p + ')^' + k + '*(' + q + ')^(' + n + '-' + k + ')');\n    var pkNum = IJS.Symja.eval('N[' + pk + ',6]');\n    IJS.Symja.print('  k=' + k + ': ' + pk + ' ≈ ' + pkNum);\n  }\n  return 'Null';\n}"
 }

+ 1 - 1
app/src/main/assets/plugins/bisection_method.json

@@ -11,5 +11,5 @@
     {"name": "tol", "label": "精度", "defaultValue": "0.0001"},
     {"name": "maxIter", "label": "最大迭代次数", "defaultValue": "50"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var fx = inputs.fx, a = parseFloat(inputs.a), b = parseFloat(inputs.b);\n  var tol = parseFloat(inputs.tol), maxIter = parseInt(inputs.maxIter);\n  // Check f(a)*f(b) < 0\n  var fa = parseFloat(Symja.eval('N[(' + fx + ')/.x->' + a + ',10]'));\n  var fb = parseFloat(Symja.eval('N[(' + fx + ')/.x->' + b + ',10]'));\n  if (fa * fb >= 0) {\n    Symja.print('错误: f(' + a + ')=' + fa + ', f(' + b + ')=' + fb);\n    Symja.print('f(a)*f(b)必须小于0');\n    return 'Null';\n  }\n  Symja.print('f(x) = ' + fx);\n  Symja.print('区间 [' + a + ', ' + b + '], 精度=' + tol);\n  Symja.print('f(' + a + ')=' + fa + ', f(' + b + ')=' + fb);\n  var c, fc;\n  for (var i = 1; i <= maxIter; i++) {\n    c = (a + b) / 2;\n    fc = parseFloat(Symja.eval('N[(' + fx + ')/.x->' + c + ',10]'));\n    if (Math.abs(fc) < tol || (b - a) / 2 < tol) {\n      Symja.print('收敛! 迭代' + i + '次, 根≈' + c + ', f(c)=' + fc);\n      return 'Null';\n    }\n    if (fa * fc < 0) { b = c; fb = fc; }\n    else { a = c; fa = fc; }\n  }\n  Symja.print('达到最大迭代次数, 根≈' + c + ', f(c)=' + fc);\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var fx = inputs.fx, a = parseFloat(inputs.a), b = parseFloat(inputs.b);\n  var tol = parseFloat(inputs.tol), maxIter = parseInt(inputs.maxIter);\n  // Check f(a)*f(b) < 0\n  var fa = parseFloat(IJS.Symja.eval('N[(' + fx + ')/.x->' + a + ',10]'));\n  var fb = parseFloat(IJS.Symja.eval('N[(' + fx + ')/.x->' + b + ',10]'));\n  if (fa * fb >= 0) {\n    IJS.Symja.print('错误: f(' + a + ')=' + fa + ', f(' + b + ')=' + fb);\n    IJS.Symja.print('f(a)*f(b)必须小于0');\n    return 'Null';\n  }\n  IJS.Symja.print('f(x) = ' + fx);\n  IJS.Symja.print('区间 [' + a + ', ' + b + '], 精度=' + tol);\n  IJS.Symja.print('f(' + a + ')=' + fa + ', f(' + b + ')=' + fb);\n  var c, fc;\n  for (var i = 1; i <= maxIter; i++) {\n    c = (a + b) / 2;\n    fc = parseFloat(IJS.Symja.eval('N[(' + fx + ')/.x->' + c + ',10]'));\n    if (Math.abs(fc) < tol || (b - a) / 2 < tol) {\n      IJS.Symja.print('收敛! 迭代' + i + '次, 根≈' + c + ', f(c)=' + fc);\n      return 'Null';\n    }\n    if (fa * fc < 0) { b = c; fb = fc; }\n    else { a = c; fa = fc; }\n  }\n  IJS.Symja.print('达到最大迭代次数, 根≈' + c + ', f(c)=' + fc);\n  return 'Null';\n}"
 }

+ 1 - 1
app/src/main/assets/plugins/chi_square.json

@@ -10,5 +10,5 @@
     {"name": "c", "label": "c", "defaultValue": "20"},
     {"name": "d", "label": "d", "defaultValue": "40"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var a = inputs.a, b = inputs.b, c = inputs.c, d = inputs.d;\n  var n = Symja.eval(a + '+' + b + '+' + c + '+' + d);\n  var num = Symja.eval('(' + a + '*' + d + '-' + b + '*' + c + ')^2');\n  var den = Symja.eval('(' + a + '+' + b + ')*(' + c + '+' + d + ')*(' + a + '+' + c + ')*(' + b + '+' + d + ')');\n  var K2 = Symja.eval(n + '*' + num + '/' + den);\n  Symja.print('n = ' + n);\n  Symja.print('K² = ' + K2);\n  Symja.print('N[K²] = ' + Symja.eval('N[' + K2 + ', 6]'));\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var a = inputs.a, b = inputs.b, c = inputs.c, d = inputs.d;\n  var n = IJS.Symja.eval(a + '+' + b + '+' + c + '+' + d);\n  var num = IJS.Symja.eval('(' + a + '*' + d + '-' + b + '*' + c + ')^2');\n  var den = IJS.Symja.eval('(' + a + '+' + b + ')*(' + c + '+' + d + ')*(' + a + '+' + c + ')*(' + b + '+' + d + ')');\n  var K2 = IJS.Symja.eval(n + '*' + num + '/' + den);\n  IJS.Symja.print('n = ' + n);\n  IJS.Symja.print('K² = ' + K2);\n  IJS.Symja.print('N[K²] = ' + IJS.Symja.eval('N[' + K2 + ', 6]'));\n  return 'Null';\n}"
 }

+ 1 - 1
app/src/main/assets/plugins/circle_equation.json

@@ -12,5 +12,5 @@
     {"name": "x3", "label": "x3", "defaultValue": "-1"},
     {"name": "y3", "label": "y3", "defaultValue": "0"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var x1=inputs.x1, y1=inputs.y1, x2=inputs.x2, y2=inputs.y2, x3=inputs.x3, y3=inputs.y3;\n  var z1 = Symja.eval(x1 + '^2+(' + y1 + ')^2');\n  var z2 = Symja.eval(x2 + '^2+(' + y2 + ')^2');\n  var z3 = Symja.eval(x3 + '^2+(' + y3 + ')^2');\n  // Use u,v instead of D,E (D is Symja's derivative operator)\n  var sol = Symja.eval('Solve[{(' + x1 + '-(' + x2 + '))*u+(' + y1 + '-(' + y2 + '))*v==' + z2 + '-(' + z1 + '), (' + x1 + '-(' + x3 + '))*u+(' + y1 + '-(' + y3 + '))*v==' + z3 + '-(' + z1 + ')}, {u, v}]');\n  Symja.print('三点: (' + x1 + ',' + y1 + '), (' + x2 + ',' + y2 + '), (' + x3 + ',' + y3 + ')');\n  Symja.print('圆方程: x²+y²+ux+vy+F=0');\n  Symja.print('u, v = ' + sol);\n  Symja.print('圆心: (-u/2, -v/2)');\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var x1=inputs.x1, y1=inputs.y1, x2=inputs.x2, y2=inputs.y2, x3=inputs.x3, y3=inputs.y3;\n  var z1 = IJS.Symja.eval(x1 + '^2+(' + y1 + ')^2');\n  var z2 = IJS.Symja.eval(x2 + '^2+(' + y2 + ')^2');\n  var z3 = IJS.Symja.eval(x3 + '^2+(' + y3 + ')^2');\n  // Use u,v instead of D,E (D is Symja's derivative operator)\n  var sol = IJS.Symja.eval('Solve[{(' + x1 + '-(' + x2 + '))*u+(' + y1 + '-(' + y2 + '))*v==' + z2 + '-(' + z1 + '), (' + x1 + '-(' + x3 + '))*u+(' + y1 + '-(' + y3 + '))*v==' + z3 + '-(' + z1 + ')}, {u, v}]');\n  IJS.Symja.print('三点: (' + x1 + ',' + y1 + '), (' + x2 + ',' + y2 + '), (' + x3 + ',' + y3 + ')');\n  IJS.Symja.print('圆方程: x²+y²+ux+vy+F=0');\n  IJS.Symja.print('u, v = ' + sol);\n  IJS.Symja.print('圆心: (-u/2, -v/2)');\n  return 'Null';\n}"
 }

+ 1 - 1
app/src/main/assets/plugins/convolution.json

@@ -10,5 +10,5 @@
     {"name": "yVals", "label": "Y的取值(逗号分隔)", "defaultValue": "0,1"},
     {"name": "yProb", "label": "Y的概率(逗号分隔)", "defaultValue": "1/2,1/2"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var xv = inputs.xVals.split(','), xp = inputs.xProb.split(',');\n  var yv = inputs.yVals.split(','), yp = inputs.yProb.split(',');\n  if (xv.length !== xp.length || yv.length !== yp.length)\n    throw new Error('取值和概率数量不一致');\n  // Verify X probabilities sum to 1\n  var sx = '0';\n  for (var i=0; i<xp.length; i++) sx = Symja.eval(sx + '+(' + xp[i].trim() + ')');\n  Symja.print('ΣP(X) = ' + sx);\n  var sy = '0';\n  for (var i=0; i<yp.length; i++) sy = Symja.eval(sy + '+(' + yp[i].trim() + ')');\n  Symja.print('ΣP(Y) = ' + sy);\n  // Build convolution map\n  var conv = {};\n  for (var i=0; i<xv.length; i++) {\n    for (var j=0; j<yv.length; j++) {\n      var sumExpr = Symja.eval(xv[i].trim() + '+' + yv[j].trim());\n      var probExpr = Symja.eval('(' + xp[i].trim() + ')*(' + yp[j].trim() + ')');\n      if (conv[sumExpr]) {\n        conv[sumExpr] = Symja.eval(conv[sumExpr] + '+' + probExpr);\n      } else {\n        conv[sumExpr] = probExpr;\n      }\n    }\n  }\n  // Sort keys and output\n  var keys = Object.keys(conv).sort(function(a,b) {\n    return parseFloat(Symja.eval('N[' + a + ',10]')) - parseFloat(Symja.eval('N[' + b + ',10]'));\n  });\n  Symja.print('');\n  Symja.print('X+Y 分布列:');\n  for (var k=0; k<keys.length; k++) {\n    var val = keys[k];\n    var prob = conv[val];\n    Symja.print('  P(X+Y=' + val + ') = ' + prob + ' ≈ ' + Symja.eval('N[' + prob + ',6]'));\n  }\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var xv = inputs.xVals.split(','), xp = inputs.xProb.split(',');\n  var yv = inputs.yVals.split(','), yp = inputs.yProb.split(',');\n  if (xv.length !== xp.length || yv.length !== yp.length)\n    throw new Error('取值和概率数量不一致');\n  // Verify X probabilities sum to 1\n  var sx = '0';\n  for (var i=0; i<xp.length; i++) sx = IJS.Symja.eval(sx + '+(' + xp[i].trim() + ')');\n  IJS.Symja.print('ΣP(X) = ' + sx);\n  var sy = '0';\n  for (var i=0; i<yp.length; i++) sy = IJS.Symja.eval(sy + '+(' + yp[i].trim() + ')');\n  IJS.Symja.print('ΣP(Y) = ' + sy);\n  // Build convolution map\n  var conv = {};\n  for (var i=0; i<xv.length; i++) {\n    for (var j=0; j<yv.length; j++) {\n      var sumExpr = IJS.Symja.eval(xv[i].trim() + '+' + yv[j].trim());\n      var probExpr = IJS.Symja.eval('(' + xp[i].trim() + ')*(' + yp[j].trim() + ')');\n      if (conv[sumExpr]) {\n        conv[sumExpr] = IJS.Symja.eval(conv[sumExpr] + '+' + probExpr);\n      } else {\n        conv[sumExpr] = probExpr;\n      }\n    }\n  }\n  // Sort keys and output\n  var keys = Object.keys(conv).sort(function(a,b) {\n    return parseFloat(IJS.Symja.eval('N[' + a + ',10]')) - parseFloat(IJS.Symja.eval('N[' + b + ',10]'));\n  });\n  IJS.Symja.print('');\n  IJS.Symja.print('X+Y 分布列:');\n  for (var k=0; k<keys.length; k++) {\n    var val = keys[k];\n    var prob = conv[val];\n    IJS.Symja.print('  P(X+Y=' + val + ') = ' + prob + ' ≈ ' + IJS.Symja.eval('N[' + prob + ',6]'));\n  }\n  return 'Null';\n}"
 }

+ 1 - 1
app/src/main/assets/plugins/cuo_wei_xiang_jian.json

@@ -10,5 +10,5 @@
     {"name": "q", "label": "公比q", "defaultValue": "2"},
     {"name": "m", "label": "指数偏移m", "defaultValue": "0"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var a=inputs.a, b=inputs.b, q=inputs.q, m=inputs.m;\n  var p = Symja.eval(m + '+1');\n  var aAdj = Symja.eval(a + '*' + q + '^' + p);\n  var bAdj = Symja.eval(b + '*' + q + '^' + p);\n  var A = Symja.eval(aAdj + '/(' + q + '-1)');\n  var B = Symja.eval('(' + bAdj + '-(' + A + '))/(' + q + '-1)');\n  Symja.print('通项: (' + a + 'n+' + b + ')*' + q + '^(n+' + m + ')');\n  Symja.print('A = ' + A + ' ≈ ' + Symja.eval('N[' + A + ',6]'));\n  Symja.print('B = ' + B + ' ≈ ' + Symja.eval('N[' + B + ',6]'));\n  Symja.print('前n项和: S_n = (' + A + 'n + ' + B + ')*' + q + '^n - (' + B + ')');\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var a=inputs.a, b=inputs.b, q=inputs.q, m=inputs.m;\n  var p = IJS.Symja.eval(m + '+1');\n  var aAdj = IJS.Symja.eval(a + '*' + q + '^' + p);\n  var bAdj = IJS.Symja.eval(b + '*' + q + '^' + p);\n  var A = IJS.Symja.eval(aAdj + '/(' + q + '-1)');\n  var B = IJS.Symja.eval('(' + bAdj + '-(' + A + '))/(' + q + '-1)');\n  IJS.Symja.print('通项: (' + a + 'n+' + b + ')*' + q + '^(n+' + m + ')');\n  IJS.Symja.print('A = ' + A + ' ≈ ' + IJS.Symja.eval('N[' + A + ',6]'));\n  IJS.Symja.print('B = ' + B + ' ≈ ' + IJS.Symja.eval('N[' + B + ',6]'));\n  IJS.Symja.print('前n项和: S_n = (' + A + 'n + ' + B + ')*' + q + '^n - (' + B + ')');\n  return 'Null';\n}"
 }

+ 1 - 1
app/src/main/assets/plugins/histogram_stats.json

@@ -10,5 +10,5 @@
     {"name": "freqs", "label": "各组频率(逗号分隔,和=1)", "defaultValue": "0.1,0.2,0.4,0.2,0.1"},
     {"name": "ptile", "label": "百分位数(可选,如0.5)", "defaultValue": "0.5"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var a = inputs.a, d = inputs.d;\n  var freqs = inputs.freqs.split(',');\n  var ptile = inputs.ptile ? parseFloat(inputs.ptile) : 0;\n  var n = freqs.length;\n  // Verify sum ≈ 1\n  var sum = '0';\n  for (var i = 0; i < n; i++) sum = Symja.eval(sum + '+' + freqs[i].trim());\n  Symja.print('频率和 = ' + sum);\n  // Mean\n  var mean = '0';\n  for (var i = 0; i < n; i++) {\n    var mid = Symja.eval(a + '+' + i + '*' + d + '+' + d + '/2');\n    mean = Symja.eval(mean + '+(' + mid + ')*(' + freqs[i].trim() + ')');\n  }\n  Symja.print('均值 = ' + mean + ' ≈ ' + Symja.eval('N[' + mean + ',6]'));\n  // Variance\n  var vari = '0';\n  for (var i = 0; i < n; i++) {\n    var mid = Symja.eval(a + '+' + i + '*' + d + '+' + d + '/2');\n    vari = Symja.eval(vari + '+(' + mid + '-(' + mean + '))^2*(' + freqs[i].trim() + ')');\n  }\n  Symja.print('方差 = ' + vari + ' ≈ ' + Symja.eval('N[' + vari + ',6]'));\n  // Median / percentile\n  if (ptile > 0) {\n    var cum = '0';\n    for (var i = 0; i < n; i++) {\n      var prevCum = cum;\n      cum = Symja.eval(cum + '+' + freqs[i].trim());\n      var cumVal = parseFloat(Symja.eval('N[' + cum + ',10]'));\n      if (cumVal >= ptile) {\n        var prevVal = parseFloat(Symja.eval('N[' + prevCum + ',10]'));\n        var fi = parseFloat(Symja.eval('N[' + freqs[i].trim() + ',10]'));\n        var pval = parseFloat(Symja.eval('N[' + a + '+' + i + '*' + d + '+(' + ptile + '-(' + prevCum + '))/(' + freqs[i].trim() + ')*' + d + ',6]'));\n        Symja.print('P' + (ptile*100) + ' = ' + Symja.eval('N[' + a + '+' + i + '*' + d + '+(' + ptile + '-(' + prevCum + '))/(' + freqs[i].trim() + ')*' + d + ',6]'));\n        break;\n      }\n    }\n  }\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var a = inputs.a, d = inputs.d;\n  var freqs = inputs.freqs.split(',');\n  var ptile = inputs.ptile ? parseFloat(inputs.ptile) : 0;\n  var n = freqs.length;\n  // Verify sum ≈ 1\n  var sum = '0';\n  for (var i = 0; i < n; i++) sum = IJS.Symja.eval(sum + '+' + freqs[i].trim());\n  IJS.Symja.print('频率和 = ' + sum);\n  // Mean\n  var mean = '0';\n  for (var i = 0; i < n; i++) {\n    var mid = IJS.Symja.eval(a + '+' + i + '*' + d + '+' + d + '/2');\n    mean = IJS.Symja.eval(mean + '+(' + mid + ')*(' + freqs[i].trim() + ')');\n  }\n  IJS.Symja.print('均值 = ' + mean + ' ≈ ' + IJS.Symja.eval('N[' + mean + ',6]'));\n  // Variance\n  var vari = '0';\n  for (var i = 0; i < n; i++) {\n    var mid = IJS.Symja.eval(a + '+' + i + '*' + d + '+' + d + '/2');\n    vari = IJS.Symja.eval(vari + '+(' + mid + '-(' + mean + '))^2*(' + freqs[i].trim() + ')');\n  }\n  IJS.Symja.print('方差 = ' + vari + ' ≈ ' + IJS.Symja.eval('N[' + vari + ',6]'));\n  // Median / percentile\n  if (ptile > 0) {\n    var cum = '0';\n    for (var i = 0; i < n; i++) {\n      var prevCum = cum;\n      cum = IJS.Symja.eval(cum + '+' + freqs[i].trim());\n      var cumVal = parseFloat(IJS.Symja.eval('N[' + cum + ',10]'));\n      if (cumVal >= ptile) {\n        var prevVal = parseFloat(IJS.Symja.eval('N[' + prevCum + ',10]'));\n        var fi = parseFloat(IJS.Symja.eval('N[' + freqs[i].trim() + ',10]'));\n        var pval = parseFloat(IJS.Symja.eval('N[' + a + '+' + i + '*' + d + '+(' + ptile + '-(' + prevCum + '))/(' + freqs[i].trim() + ')*' + d + ',6]'));\n        IJS.Symja.print('P' + (ptile*100) + ' = ' + IJS.Symja.eval('N[' + a + '+' + i + '*' + d + '+(' + ptile + '-(' + prevCum + '))/(' + freqs[i].trim() + ')*' + d + ',6]'));\n        break;\n      }\n    }\n  }\n  return 'Null';\n}"
 }

+ 1 - 1
app/src/main/assets/plugins/hypergeometric_distribution.json

@@ -9,5 +9,5 @@
     {"name": "M", "label": "成功数M", "defaultValue": "4"},
     {"name": "n", "label": "抽取数n", "defaultValue": "3"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var N = parseInt(inputs.N), M = parseInt(inputs.M), n = parseInt(inputs.n);\n  var EX = Symja.eval(n + '*' + M + '/' + N);\n  var DX = Symja.eval(n + '*' + M + '*(' + N + '-' + M + ')*(' + N + '-' + n + ')/(' + N + '^2*(' + N + '-1))');\n  var minK = Math.max(0, n - (N - M));\n  var maxK = Math.min(n, M);\n  Symja.print('X ~ H(' + N + ', ' + M + ', ' + n + ')');\n  Symja.print('E(X) = ' + EX + ' = ' + Symja.eval('N[' + EX + ',6]'));\n  Symja.print('D(X) = ' + DX + ' = ' + Symja.eval('N[' + DX + ',6]'));\n  Symja.print('k范围: ' + minK + ' ≤ k ≤ ' + maxK);\n  Symja.print('');\n  Symja.print('分布列 P(X=k):');\n  for (var k = minK; k <= maxK; k++) {\n    var num = Symja.eval('Binomial[' + M + ',' + k + ']*Binomial[' + (N-M) + ',' + (n-k) + ']');\n    var den = Symja.eval('Binomial[' + N + ',' + n + ']');\n    var pk = Symja.eval(num + '/' + den);\n    Symja.print('  k=' + k + ': ' + pk + ' ≈ ' + Symja.eval('N[' + pk + ',6]'));\n  }\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var N = parseInt(inputs.N), M = parseInt(inputs.M), n = parseInt(inputs.n);\n  var EX = IJS.Symja.eval(n + '*' + M + '/' + N);\n  var DX = IJS.Symja.eval(n + '*' + M + '*(' + N + '-' + M + ')*(' + N + '-' + n + ')/(' + N + '^2*(' + N + '-1))');\n  var minK = Math.max(0, n - (N - M));\n  var maxK = Math.min(n, M);\n  IJS.Symja.print('X ~ H(' + N + ', ' + M + ', ' + n + ')');\n  IJS.Symja.print('E(X) = ' + EX + ' = ' + IJS.Symja.eval('N[' + EX + ',6]'));\n  IJS.Symja.print('D(X) = ' + DX + ' = ' + IJS.Symja.eval('N[' + DX + ',6]'));\n  IJS.Symja.print('k范围: ' + minK + ' ≤ k ≤ ' + maxK);\n  IJS.Symja.print('');\n  IJS.Symja.print('分布列 P(X=k):');\n  for (var k = minK; k <= maxK; k++) {\n    var num = IJS.Symja.eval('Binomial[' + M + ',' + k + ']*Binomial[' + (N-M) + ',' + (n-k) + ']');\n    var den = IJS.Symja.eval('Binomial[' + N + ',' + n + ']');\n    var pk = IJS.Symja.eval(num + '/' + den);\n    IJS.Symja.print('  k=' + k + ': ' + pk + ' ≈ ' + IJS.Symja.eval('N[' + pk + ',6]'));\n  }\n  return 'Null';\n}"
 }

+ 1 - 1
app/src/main/assets/plugins/linear_regression.json

@@ -8,5 +8,5 @@
     {"name": "xs", "label": "x值(逗号分隔)", "defaultValue": "1,2,3,4,5"},
     {"name": "ys", "label": "y值(逗号分隔)", "defaultValue": "2,4,5,4,5"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var xs = inputs.xs.split(','), ys = inputs.ys.split(',');\n  var n = xs.length;\n  if (n !== ys.length) throw new Error('x和y数量不一致');\n  var Sx='0', Sy='0', Sx2='0', Sy2='0', Sxy='0';\n  for (var i=0; i<n; i++) {\n    var xi = xs[i].trim(), yi = ys[i].trim();\n    Sx = Symja.eval(Sx + '+' + xi);\n    Sy = Symja.eval(Sy + '+' + yi);\n    Sx2 = Symja.eval(Sx2 + '+' + xi + '^2');\n    Sy2 = Symja.eval(Sy2 + '+' + yi + '^2');\n    Sxy = Symja.eval(Sxy + '+' + xi + '*' + yi);\n  }\n  var xBar = Symja.eval(Sx + '/' + n);\n  var yBar = Symja.eval(Sy + '/' + n);\n  var sigX = Symja.eval(Sx2 + '-' + n + '*(' + xBar + ')^2');\n  var sigY = Symja.eval(Sy2 + '-' + n + '*(' + yBar + ')^2');\n  var sigXY = Symja.eval(Sxy + '-' + n + '*(' + xBar + ')*(' + yBar + ')');\n  var r = Symja.eval(sigXY + '/Sqrt[' + sigX + '*' + sigY + ']');\n  var b = Symja.eval(sigXY + '/' + sigX);\n  var a = Symja.eval(yBar + '-(' + b + ')*(' + xBar + ')');\n  Symja.print('n = ' + n);\n  Symja.print('x̄ = ' + xBar + ' ≈ ' + Symja.eval('N[' + xBar + ',6]'));\n  Symja.print('ȳ = ' + yBar + ' ≈ ' + Symja.eval('N[' + yBar + ',6]'));\n  Symja.print('σx² = ' + sigX);\n  Symja.print('σy² = ' + sigY);\n  Symja.print('σxy = ' + sigXY);\n  Symja.print('r = ' + r + ' ≈ ' + Symja.eval('N[' + r + ',6]'));\n  Symja.print('b = ' + b + ' ≈ ' + Symja.eval('N[' + b + ',6]'));\n  Symja.print('a = ' + a + ' ≈ ' + Symja.eval('N[' + a + ',6]'));\n  Symja.print('回归方程: y = ' + a + ' + ' + b + 'x');\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var xs = inputs.xs.split(','), ys = inputs.ys.split(',');\n  var n = xs.length;\n  if (n !== ys.length) throw new Error('x和y数量不一致');\n  var Sx='0', Sy='0', Sx2='0', Sy2='0', Sxy='0';\n  for (var i=0; i<n; i++) {\n    var xi = xs[i].trim(), yi = ys[i].trim();\n    Sx = IJS.Symja.eval(Sx + '+' + xi);\n    Sy = IJS.Symja.eval(Sy + '+' + yi);\n    Sx2 = IJS.Symja.eval(Sx2 + '+' + xi + '^2');\n    Sy2 = IJS.Symja.eval(Sy2 + '+' + yi + '^2');\n    Sxy = IJS.Symja.eval(Sxy + '+' + xi + '*' + yi);\n  }\n  var xBar = IJS.Symja.eval(Sx + '/' + n);\n  var yBar = IJS.Symja.eval(Sy + '/' + n);\n  var sigX = IJS.Symja.eval(Sx2 + '-' + n + '*(' + xBar + ')^2');\n  var sigY = IJS.Symja.eval(Sy2 + '-' + n + '*(' + yBar + ')^2');\n  var sigXY = IJS.Symja.eval(Sxy + '-' + n + '*(' + xBar + ')*(' + yBar + ')');\n  var r = IJS.Symja.eval(sigXY + '/Sqrt[' + sigX + '*' + sigY + ']');\n  var b = IJS.Symja.eval(sigXY + '/' + sigX);\n  var a = IJS.Symja.eval(yBar + '-(' + b + ')*(' + xBar + ')');\n  IJS.Symja.print('n = ' + n);\n  IJS.Symja.print('x̄ = ' + xBar + ' ≈ ' + IJS.Symja.eval('N[' + xBar + ',6]'));\n  IJS.Symja.print('ȳ = ' + yBar + ' ≈ ' + IJS.Symja.eval('N[' + yBar + ',6]'));\n  IJS.Symja.print('σx² = ' + sigX);\n  IJS.Symja.print('σy² = ' + sigY);\n  IJS.Symja.print('σxy = ' + sigXY);\n  IJS.Symja.print('r = ' + r + ' ≈ ' + IJS.Symja.eval('N[' + r + ',6]'));\n  IJS.Symja.print('b = ' + b + ' ≈ ' + IJS.Symja.eval('N[' + b + ',6]'));\n  IJS.Symja.print('a = ' + a + ' ≈ ' + IJS.Symja.eval('N[' + a + ',6]'));\n  IJS.Symja.print('回归方程: y = ' + a + ' + ' + b + 'x');\n  return 'Null';\n}"
 }

+ 1 - 1
app/src/main/assets/plugins/mixed_variance.json

@@ -9,5 +9,5 @@
     {"name": "means", "label": "各层均值 (逗号分隔)", "defaultValue": "75,80,85"},
     {"name": "variances", "label": "各层方差 (逗号分隔)", "defaultValue": "100,120,90"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var sizes = inputs.sizes.split(',');\n  var means = inputs.means.split(',');\n  var vars = inputs.variances.split(',');\n  if (sizes.length !== means.length || sizes.length !== vars.length) {\n    throw new Error('各组数据数量不一致');\n  }\n  var n = sizes.length;\n  var totalSize = '0';\n  var weightedSum = '0';\n  for (var i = 0; i < n; i++) {\n    totalSize = Symja.eval(totalSize + '+' + sizes[i].trim());\n    weightedSum = Symja.eval(weightedSum + '+' + sizes[i].trim() + '*(' + means[i].trim() + ')');\n  }\n  var overallMean = Symja.eval(weightedSum + '/' + totalSize);\n  var mixedVar = '0';\n  for (var i = 0; i < n; i++) {\n    var diff = Symja.eval('(' + means[i].trim() + '-(' + overallMean + '))^2');\n    mixedVar = Symja.eval(mixedVar + '+' + sizes[i].trim() + '*(' + vars[i].trim() + '+' + diff + ')');\n  }\n  mixedVar = Symja.eval(mixedVar + '/' + totalSize);\n  Symja.print('总样本量: ' + totalSize);\n  Symja.print('总均值: ' + overallMean + ' ≈ ' + Symja.eval('N[' + overallMean + ', 6]'));\n  Symja.print('混合方差: ' + mixedVar + ' ≈ ' + Symja.eval('N[' + mixedVar + ', 6]'));\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var sizes = inputs.sizes.split(',');\n  var means = inputs.means.split(',');\n  var vars = inputs.variances.split(',');\n  if (sizes.length !== means.length || sizes.length !== vars.length) {\n    throw new Error('各组数据数量不一致');\n  }\n  var n = sizes.length;\n  var totalSize = '0';\n  var weightedSum = '0';\n  for (var i = 0; i < n; i++) {\n    totalSize = IJS.Symja.eval(totalSize + '+' + sizes[i].trim());\n    weightedSum = IJS.Symja.eval(weightedSum + '+' + sizes[i].trim() + '*(' + means[i].trim() + ')');\n  }\n  var overallMean = IJS.Symja.eval(weightedSum + '/' + totalSize);\n  var mixedVar = '0';\n  for (var i = 0; i < n; i++) {\n    var diff = IJS.Symja.eval('(' + means[i].trim() + '-(' + overallMean + '))^2');\n    mixedVar = IJS.Symja.eval(mixedVar + '+' + sizes[i].trim() + '*(' + vars[i].trim() + '+' + diff + ')');\n  }\n  mixedVar = IJS.Symja.eval(mixedVar + '/' + totalSize);\n  IJS.Symja.print('总样本量: ' + totalSize);\n  IJS.Symja.print('总均值: ' + overallMean + ' ≈ ' + IJS.Symja.eval('N[' + overallMean + ', 6]'));\n  IJS.Symja.print('混合方差: ' + mixedVar + ' ≈ ' + IJS.Symja.eval('N[' + mixedVar + ', 6]'));\n  return 'Null';\n}"
 }

Разница между файлами не показана из-за своего большого размера
+ 8 - 0
app/src/main/assets/plugins/prime_snake.json


+ 1 - 1
app/src/main/assets/plugins/symmetry_ll.json

@@ -12,5 +12,5 @@
     {"name": "B", "label": "对称轴Ax+By+C=0的B", "defaultValue": "0"},
     {"name": "C", "label": "对称轴Ax+By+C=0的C", "defaultValue": "0"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var P=inputs.P, Q=inputs.Q, R=inputs.R, A=inputs.A, B=inputs.B, C=inputs.C;\n  var denom = Symja.eval('(' + A + ')^2 + (' + B + ')^2');\n  var k = Symja.eval('2*((' + P + ')*(' + A + ')+(' + Q + ')*(' + B + '))/(' + denom + ')');\n  var newA = Symja.eval('(' + P + ')-(' + A + ')*(' + k + ')');\n  var newB = Symja.eval('(' + Q + ')-(' + B + ')*(' + k + ')');\n  var newC = Symja.eval('(' + R + ')-(' + C + ')*(' + k + ')');\n  Symja.print('原直线: ' + P + 'x+' + Q + 'y+' + R + '=0');\n  Symja.print('对称轴: ' + A + 'x+' + B + 'y+' + C + '=0');\n  Symja.print('对称直线: ' + newA + 'x + ' + newB + 'y + ' + newC + ' = 0');\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var P=inputs.P, Q=inputs.Q, R=inputs.R, A=inputs.A, B=inputs.B, C=inputs.C;\n  var denom = IJS.Symja.eval('(' + A + ')^2 + (' + B + ')^2');\n  var k = IJS.Symja.eval('2*((' + P + ')*(' + A + ')+(' + Q + ')*(' + B + '))/(' + denom + ')');\n  var newA = IJS.Symja.eval('(' + P + ')-(' + A + ')*(' + k + ')');\n  var newB = IJS.Symja.eval('(' + Q + ')-(' + B + ')*(' + k + ')');\n  var newC = IJS.Symja.eval('(' + R + ')-(' + C + ')*(' + k + ')');\n  IJS.Symja.print('原直线: ' + P + 'x+' + Q + 'y+' + R + '=0');\n  IJS.Symja.print('对称轴: ' + A + 'x+' + B + 'y+' + C + '=0');\n  IJS.Symja.print('对称直线: ' + newA + 'x + ' + newB + 'y + ' + newC + ' = 0');\n  return 'Null';\n}"
 }

+ 1 - 1
app/src/main/assets/plugins/symmetry_lp.json

@@ -11,5 +11,5 @@
     {"name": "x", "label": "对称中心x", "defaultValue": "0"},
     {"name": "y", "label": "对称中心y", "defaultValue": "0"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var A = inputs.A, B = inputs.B, C = inputs.C, x = inputs.x, y = inputs.y;\n  var newC = Symja.eval('-2*' + A + '*' + x + ' - 2*' + B + '*' + y + ' - (' + C + ')');\n  Symja.print('直线 ' + A + 'x+' + B + 'y+' + C + '=0');\n  Symja.print('关于点(' + x + ', ' + y + ')的对称直线:');\n  Symja.print(A + 'x + ' + B + 'y + ' + newC + ' = 0');\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var A = inputs.A, B = inputs.B, C = inputs.C, x = inputs.x, y = inputs.y;\n  var newC = IJS.Symja.eval('-2*' + A + '*' + x + ' - 2*' + B + '*' + y + ' - (' + C + ')');\n  IJS.Symja.print('直线 ' + A + 'x+' + B + 'y+' + C + '=0');\n  IJS.Symja.print('关于点(' + x + ', ' + y + ')的对称直线:');\n  IJS.Symja.print(A + 'x + ' + B + 'y + ' + newC + ' = 0');\n  return 'Null';\n}"
 }

+ 1 - 1
app/src/main/assets/plugins/symmetry_pl.json

@@ -11,5 +11,5 @@
     {"name": "B", "label": "直线Ax+By+C=0的B", "defaultValue": "-1"},
     {"name": "C", "label": "直线Ax+By+C=0的C", "defaultValue": "0"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var x = inputs.x, y = inputs.y, A = inputs.A, B = inputs.B, C = inputs.C;\n  // A*xp + B*yp = -(A*x + B*y + 2*C)\n  // B*xp - A*yp = B*x - A*y\n  var rhs1 = Symja.eval('-(' + A + '*(' + x + ') + ' + B + '*(' + y + ') + 2*(' + C + '))');\n  var rhs2 = Symja.eval(B + '*(' + x + ') - ' + A + '*(' + y + ')');\n  var eq1 = A + '*xp + ' + B + '*yp == ' + rhs1;\n  var eq2 = B + '*xp - ' + A + '*yp == ' + rhs2;\n  var sol = Symja.solve('{' + eq1 + ', ' + eq2 + '}', '{xp, yp}');\n  Symja.print('点(' + x + ', ' + y + ') 关于直线 ' + A + 'x+' + B + 'y+' + C + '=0');\n  Symja.print('对称点: ' + sol);\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var x = inputs.x, y = inputs.y, A = inputs.A, B = inputs.B, C = inputs.C;\n  // A*xp + B*yp = -(A*x + B*y + 2*C)\n  // B*xp - A*yp = B*x - A*y\n  var rhs1 = IJS.Symja.eval('-(' + A + '*(' + x + ') + ' + B + '*(' + y + ') + 2*(' + C + '))');\n  var rhs2 = IJS.Symja.eval(B + '*(' + x + ') - ' + A + '*(' + y + ')');\n  var eq1 = A + '*xp + ' + B + '*yp == ' + rhs1;\n  var eq2 = B + '*xp - ' + A + '*yp == ' + rhs2;\n  var sol = IJS.Symja.solve('{' + eq1 + ', ' + eq2 + '}', '{xp, yp}');\n  IJS.Symja.print('点(' + x + ', ' + y + ') 关于直线 ' + A + 'x+' + B + 'y+' + C + '=0');\n  IJS.Symja.print('对称点: ' + sol);\n  return 'Null';\n}"
 }

+ 1 - 1
app/src/main/assets/plugins/vector_normal.json

@@ -15,5 +15,5 @@
     {"name": "cy", "label": "C.y", "defaultValue": "0"},
     {"name": "cz", "label": "C.z", "defaultValue": "1"}
   ],
-  "jsCode": "function calculate(inputs, Symja) {\n  var ax=inputs.ax,ay=inputs.ay,az=inputs.az;\n  var bx=inputs.bx,by=inputs.by,bz=inputs.bz;\n  var cx=inputs.cx,cy=inputs.cy,cz=inputs.cz;\n  // v1 = B - A, v2 = C - A\n  var v1x = Symja.eval('Simplify[(' + bx + ')-(' + ax + ')]');\n  var v1y = Symja.eval('Simplify[(' + by + ')-(' + ay + ')]');\n  var v1z = Symja.eval('Simplify[(' + bz + ')-(' + az + ')]');\n  var v2x = Symja.eval('Simplify[(' + cx + ')-(' + ax + ')]');\n  var v2y = Symja.eval('Simplify[(' + cy + ')-(' + ay + ')]');\n  var v2z = Symja.eval('Simplify[(' + cz + ')-(' + az + ')]');\n  // Cross product: v1 × v2 with Simplify\n  var nx = Symja.eval('Simplify[(' + v1y + ')*(' + v2z + ')-(' + v1z + ')*(' + v2y + ')]');\n  var ny = Symja.eval('Simplify[(' + v1z + ')*(' + v2x + ')-(' + v1x + ')*(' + v2z + ')]');\n  var nz = Symja.eval('Simplify[(' + v1x + ')*(' + v2y + ')-(' + v1y + ')*(' + v2x + ')]');\n  Symja.print('A = (' + ax + ', ' + ay + ', ' + az + ')');\n  Symja.print('B = (' + bx + ', ' + by + ', ' + bz + ')');\n  Symja.print('C = (' + cx + ', ' + cy + ', ' + cz + ')');\n  Symja.print('AB = (' + v1x + ', ' + v1y + ', ' + v1z + ')');\n  Symja.print('AC = (' + v2x + ', ' + v2y + ', ' + v2z + ')');\n  Symja.print('法向量 n = AB × AC = (' + nx + ', ' + ny + ', ' + nz + ')');\n  return 'Null';\n}"
+  "jsCode": "function calculate(inputs, IJS) {\n  var ax=inputs.ax,ay=inputs.ay,az=inputs.az;\n  var bx=inputs.bx,by=inputs.by,bz=inputs.bz;\n  var cx=inputs.cx,cy=inputs.cy,cz=inputs.cz;\n  // v1 = B - A, v2 = C - A\n  var v1x = IJS.Symja.eval('Simplify[(' + bx + ')-(' + ax + ')]');\n  var v1y = IJS.Symja.eval('Simplify[(' + by + ')-(' + ay + ')]');\n  var v1z = IJS.Symja.eval('Simplify[(' + bz + ')-(' + az + ')]');\n  var v2x = IJS.Symja.eval('Simplify[(' + cx + ')-(' + ax + ')]');\n  var v2y = IJS.Symja.eval('Simplify[(' + cy + ')-(' + ay + ')]');\n  var v2z = IJS.Symja.eval('Simplify[(' + cz + ')-(' + az + ')]');\n  // Cross product: v1 × v2 with Simplify\n  var nx = IJS.Symja.eval('Simplify[(' + v1y + ')*(' + v2z + ')-(' + v1z + ')*(' + v2y + ')]');\n  var ny = IJS.Symja.eval('Simplify[(' + v1z + ')*(' + v2x + ')-(' + v1x + ')*(' + v2z + ')]');\n  var nz = IJS.Symja.eval('Simplify[(' + v1x + ')*(' + v2y + ')-(' + v1y + ')*(' + v2x + ')]');\n  IJS.Symja.print('A = (' + ax + ', ' + ay + ', ' + az + ')');\n  IJS.Symja.print('B = (' + bx + ', ' + by + ', ' + bz + ')');\n  IJS.Symja.print('C = (' + cx + ', ' + cy + ', ' + cz + ')');\n  IJS.Symja.print('AB = (' + v1x + ', ' + v1y + ', ' + v1z + ')');\n  IJS.Symja.print('AC = (' + v2x + ', ' + v2y + ', ' + v2z + ')');\n  IJS.Symja.print('法向量 n = AB × AC = (' + nx + ', ' + ny + ', ' + nz + ')');\n  return 'Null';\n}"
 }

+ 48 - 0
app/src/main/java/com/nonvioce/ijs/MainActivity.java

@@ -391,6 +391,54 @@ public class MainActivity extends AppCompatActivity {
             return symjaEngine.evaluatePlot(expression);
         }
 
+        // ── IJS.io 文件读写 (应用数据目录) ─────────────────────
+
+        @JavascriptInterface
+        public String ioRead(String filename) {
+            try {
+                java.io.File file = new java.io.File(getFilesDir(), filename);
+                if (!file.exists()) return "";
+                java.io.BufferedReader r = new java.io.BufferedReader(
+                    new java.io.InputStreamReader(new java.io.FileInputStream(file)));
+                StringBuilder sb = new StringBuilder();
+                String line;
+                while ((line = r.readLine()) != null) sb.append(line).append("\n");
+                r.close();
+                return sb.toString();
+            } catch (Exception e) {
+                return "";
+            }
+        }
+
+        @JavascriptInterface
+        public void ioWrite(String filename, String content) {
+            try {
+                java.io.File file = new java.io.File(getFilesDir(), filename);
+                java.io.FileWriter w = new java.io.FileWriter(file);
+                w.write(content);
+                w.close();
+            } catch (Exception ignored) {}
+        }
+
+        @JavascriptInterface
+        public String ioList() {
+            try {
+                String[] files = getFilesDir().list();
+                org.json.JSONArray arr = new org.json.JSONArray();
+                for (String f : files) arr.put(f);
+                return arr.toString();
+            } catch (Exception e) {
+                return "[]";
+            }
+        }
+
+        @JavascriptInterface
+        public void ioDelete(String filename) {
+            try {
+                new java.io.File(getFilesDir(), filename).delete();
+            } catch (Exception ignored) {}
+        }
+
         @JavascriptInterface
         public String savePlotImage(String base64, String filename) {
             try {

+ 49 - 0
app/src/main/java/com/nonvioce/ijs/McpServer.java

@@ -741,6 +741,11 @@ public class McpServer extends NanoHTTPD {
             "application/json",
             "Recent expressions evaluated via MCP and their results"));
 
+        resources.put(resource("api://plugin",
+            "Plugin API Reference",
+            "application/json",
+            "Complete IJS API available to plugins: IJS.Symja (math), IJS.canvas (drawing), IJS.input (touch), IJS.io (file I/O), IJS.audio (sound), IJS.ui (panel control). Libraries: lodash, seedrandom, Matter.js"));
+
         JSONObject result = new JSONObject();
         result.put("resources", resources);
         return result;
@@ -767,6 +772,10 @@ public class McpServer extends NanoHTTPD {
                 contents.put(resourceContent(uri, "application/json", historyJson.toString(2)));
                 break;
             }
+            case "api://plugin": {
+                contents.put(resourceContent(uri, "application/json", getPluginApiReference()));
+                break;
+            }
             default:
                 throw new JSONException("Unknown resource URI: " + uri);
         }
@@ -879,6 +888,46 @@ public class McpServer extends NanoHTTPD {
         return newFixedLengthResponse(status, "application/json", resp.toString());
     }
 
+    /** API reference for plugins */
+    private String getPluginApiReference() {
+        try {
+            JSONObject api = new JSONObject();
+            api.put("version", "2.0");
+            api.put("namespace", "IJS");
+            api.put("plugin_modes", "inline (dialog) or fullscreen (interactive panel)");
+            api.put("libraries", "lodash 4.17.21, seedrandom 3.0.5, Matter.js 0.19.0");
+            JSONObject symja = new JSONObject();
+            symja.put("print", "(msg) - console output");
+            symja.put("eval", "(expr) - evaluate Symja expression");
+            symja.put("differentiate", "(expr,var) - D[expr,var]");
+            symja.put("integrate", "(expr,var) - Integrate[expr,var]");
+            symja.put("solve", "(eq,var) - Solve[eq,var]");
+            symja.put("simplify", "(expr) - Simplify[expr]");
+            symja.put("expand", "(expr) - Expand[expr]");
+            symja.put("factor", "(expr) - Factor[expr]");
+            symja.put("numeric", "(expr,prec) - N[expr,prec]");
+            symja.put("plot", "(func,var,xMin,xMax) - plot function");
+            symja.put("parametricPlot", "(xf,yf,t,tMin,tMax) - parametric plot");
+            symja.put("listPlot", "(points) - scatter plot");
+            symja.put("toTeX", "(expr) - LaTeX conversion");
+            symja.put("gcd", "(a,b) - GCD");
+            symja.put("lcm", "(a,b) - LCM");
+            symja.put("isPrime", "(n) - primality test");
+            symja.put("fibonacci", "(n) - nth Fibonacci");
+            symja.put("binomial", "(n,k) - binomial coefficient");
+            symja.put("matrix", "{det,inverse,transpose,eigenvalues,rank}");
+            api.put("IJS.Symja", symja);
+            api.put("IJS.canvas", "create(w,h), getContext(), getSize(), clear(), fill(color), size(w,h), loop(cb), stop()");
+            api.put("IJS.input", "onTouchStart(cb), onTouchMove(cb), onTouchEnd(cb)");
+            api.put("IJS.io", "read(filename), write(filename,content), list(), delete(filename)");
+            api.put("IJS.audio", "beep(freq,dur,type), playNote(note,oct,dur), playMelody(melody,tempo)");
+            api.put("IJS.ui", "close(), setBackground(color)");
+            return api.toString(2);
+        } catch (Exception e) {
+            return "{}";
+        }
+    }
+
     /** Gracefully stop the server. */
     public void stopServer() {
         initialized = false;

Некоторые файлы не были показаны из-за большого количества измененных файлов