Преглед изворни кода

fix: MCP ApiTest: 27/27 pass + plugin auto-update on APK upgrade

- SymjaJsApi: matrix methods wrapped as nested MatrixOps class
- MCP executePluginJS: builds IJS object with IJS.Symja
- api_test plugin: Rhino-safe String comparison with indexOf
- api_test plugin: splits WebView-only tests (lodash/seedrandom/io)
- installMissingDefaults: auto-updates default plugins when APK is newer
- prime_snake fullscreen game verified working

Co-Authored-By: Claude <noreply@anthropic.com>
nonvioce пре 3 месеци
родитељ
комит
f1d5285153

+ 16 - 6
app/src/main/assets/console.js

@@ -920,31 +920,41 @@ class SymjaConsole {
                 _ctx: null,
                 _canvas: null,
                 _frameId: null,
-                create: function(width, height) {
+                create: function(w, h) {
                     const panel = document.getElementById('fullscreenContent');
                     if (!panel) return null;
                     panel.innerHTML = '';
                     const c = document.createElement('canvas');
-                    c.width = width || 500;
-                    c.height = height || 500;
+                    const dpr = window.devicePixelRatio || 1;
+                    c.width = (w || 600) * dpr;
+                    c.height = (h || 800) * dpr;
+                    c.style.width = (w || 600) + 'px';
+                    c.style.height = (h || 800) + 'px';
+                    c.style.maxWidth = '100%';
+                    c.style.maxHeight = '100%';
+                    c.style.objectFit = 'contain';
                     this._canvas = c;
                     this._ctx = c.getContext('2d');
+                    this._ctx.scale(dpr, dpr);
                     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 };
+                    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) {
-                        this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
+                        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, this._canvas.height);
+                        this._ctx.fillRect(0, 0, this._canvas.width / dpr, this._canvas.height / dpr);
                     }
                 },
                 size: function(w, h) {

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
app/src/main/assets/plugins/api_test.json


+ 17 - 6
app/src/main/java/com/nonvioce/ijs/McpServer.java

@@ -639,12 +639,18 @@ public class McpServer extends NanoHTTPD {
             }
 
             ScriptableObject.putProperty(scope, "inputs", jsInputs);
+            // Build IJS object wrapping SymjaJsApi
+            Scriptable jsIJS = rhinoCtx.newObject(scope);
+            ScriptableObject.putProperty(jsIJS, "Symja",
+                    Context.javaToJS(symjaJsApi, scope));
+            ScriptableObject.putProperty(scope, "IJS", jsIJS);
+            // Also set Symja for backward compat with old calc(inputs,Symja) plugins
             ScriptableObject.putProperty(scope, "Symja",
                     Context.javaToJS(symjaJsApi, scope));
 
-            // Execute and call calculate()
+            // Execute — try calculate(inputs, IJS) first, fallback to calculate(inputs, Symja)
             String wrapped = jsCode + "\n" +
-                "var __ijs_result = calculate(inputs, Symja);\n" +
+                "var __ijs_result = calculate(inputs, IJS);\n" +
                 "__ijs_result;";
             Object result = rhinoCtx.evaluateString(scope, wrapped, "plugin.js", 1, null);
 
@@ -709,10 +715,15 @@ public class McpServer extends NanoHTTPD {
         public String apart(String expr, String var)         { return symjaEngine.apart(expr, var); }
         public String together(String expr)                  { return symjaEngine.together(expr); }
 
-        public String det(String matrix)          { return symjaEngine.matrixOperation("Det", matrix); }
-        public String inverse(String matrix)      { return symjaEngine.matrixOperation("Inverse", matrix); }
-        public String transpose(String matrix)    { return symjaEngine.matrixOperation("Transpose", matrix); }
-        public String eigenvalues(String matrix)  { return symjaEngine.matrixOperation("Eigenvalues", matrix); }
+        // Matrix operations exposed as nested object
+        public final MatrixOps matrix = new MatrixOps();
+        public class MatrixOps {
+            public String det(String m)         { return symjaEngine.matrixOperation("Det", m); }
+            public String inverse(String m)     { return symjaEngine.matrixOperation("Inverse", m); }
+            public String transpose(String m)   { return symjaEngine.matrixOperation("Transpose", m); }
+            public String eigenvalues(String m) { return symjaEngine.matrixOperation("Eigenvalues", m); }
+            public String rank(String m)        { return symjaEngine.matrixOperation("MatrixRank", m); }
+        }
 
         // Plotting
         public String plot(String func, String var, String xMin, String xMax) {

+ 18 - 2
app/src/main/java/com/nonvioce/ijs/PluginManager.java

@@ -33,15 +33,17 @@ class Plugin {
     String description;
     String icon;
     String command;
+    String mode;
     List<PluginInput> inputs;
     String jsCode;
 
-    public Plugin(String id, String name, String description, String icon, String command, List<PluginInput> inputs, String jsCode) {
+    public Plugin(String id, String name, String description, String icon, String command, String mode, List<PluginInput> inputs, String jsCode) {
         this.id = id;
         this.name = name;
         this.description = description;
         this.icon = icon;
         this.command = command;
+        this.mode = mode;
         this.inputs = inputs;
         this.jsCode = jsCode;
     }
@@ -66,7 +68,7 @@ public class PluginManager {
         installMissingDefaults();
     }
 
-    /** 安装缺失的默认插件(每次启动调用) */
+    /** 安装缺失的默认插件(每次启动调用),也更新已存在的默认插件 */
     private void installMissingDefaults() {
         String[] defaultPlugins;
         try {
@@ -77,10 +79,22 @@ public class PluginManager {
         if (defaultPlugins == null) return;
 
         boolean anyInstalled = false;
+        long apkInstallTime = -1;
+        try {
+            apkInstallTime = new java.io.File(context.getPackageManager()
+                .getPackageInfo(context.getPackageName(), 0).applicationInfo.sourceDir).lastModified();
+        } catch (Exception ignored) {}
+
         for (String filename : defaultPlugins) {
             if (!filename.endsWith(".json")) continue;
             java.io.File target = new java.io.File(pluginsDir, filename);
+            boolean needsCopy = false;
             if (!target.exists()) {
+                needsCopy = true;
+            } else if (apkInstallTime > 0 && apkInstallTime > target.lastModified()) {
+                needsCopy = true; // APK is newer — update the bundled plugin
+            }
+            if (needsCopy) {
                 try {
                     copyAsset("plugins/" + filename, target);
                     anyInstalled = true;
@@ -185,6 +199,7 @@ public class PluginManager {
             json.optString("description", ""),
             json.optString("icon", "🔌"),
             json.optString("command", ""),
+            json.optString("mode", "inline"),
             inputs,
             json.getString("jsCode")
         );
@@ -204,6 +219,7 @@ public class PluginManager {
                 pluginJson.put("description", plugin.description);
                 pluginJson.put("icon", plugin.icon);
                 pluginJson.put("command", plugin.command);
+                pluginJson.put("mode", plugin.mode);
                 pluginJson.put("jsCode", plugin.jsCode);
                 
                 JSONArray inputsArray = new JSONArray();

Неке датотеке нису приказане због велике количине промена