diff --git a/README.md b/README.md index 6e6d93d..7734e92 100644 --- a/README.md +++ b/README.md @@ -631,6 +631,13 @@ data: [DONE] ## Changelog +### v1.3.1 (2026-05-19) +- **Auto-Execute** — AI actions now deploy files, build, and install APKs automatically (no manual button taps) +- **Keep Screen On** — toggle prevents Android screen sleep while agent is working +- **Auto-Deploy toggle** — enable/disable automatic file deployment from AI responses +- **Status toasts** — visual feedback for deploy/build/install progress +- New "Terminal & Agent" settings section + ### v1.3.0 (2026-05-19) - **Full Terminal** — interactive shell on your Android device, execute real commands - **Deploy Files** — AI-generated code saved to device with one-tap Deploy button diff --git a/android/app/build.gradle b/android/app/build.gradle index 9662f6c..a64a8d6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "ai.z.chat" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 7 - versionName "1.3.0" + versionCode 8 + versionName "1.3.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 00d3b2f..aaaa5f5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + { + getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + }); + + PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); + wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "zai-chat:wakelock"); + wakeLock.acquire(12 * 60 * 60 * 1000L); + isHeld = true; + + call.resolve(new JSObject().put("held", true)); + } catch (Exception e) { + call.reject("Wake lock failed: " + e.getMessage()); + } + } + + @PluginMethod + public void release(PluginCall call) { + try { + getActivity().runOnUiThread(() -> { + getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + }); + + if (wakeLock != null && wakeLock.isHeld()) { + wakeLock.release(); + } + wakeLock = null; + isHeld = false; + + call.resolve(new JSObject().put("held", false)); + } catch (Exception e) { + call.reject("Wake release failed: " + e.getMessage()); + } + } + + @PluginMethod + public void isHeld(PluginCall call) { + call.resolve(new JSObject().put("held", isHeld)); + } +} diff --git a/package.json b/package.json index d1c165b..7c5b509 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zai-chat", - "version": "1.3.0", + "version": "1.3.1", "description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan", "main": "index.js", "scripts": { diff --git a/www/index.html b/www/index.html index 861ee74..2a64f2e 100644 --- a/www/index.html +++ b/www/index.html @@ -236,6 +236,25 @@ +
+

Terminal & Agent

+
+ + +
+ AI-generated code auto-deploys to device in Coding/Agentic modes +
+ + +
+ Prevents screen sleep while agent is working +

Appearance

@@ -253,13 +272,24 @@

About

-

Z.AI Chat v1.3.0

+

Z.AI Chat v1.3.1

Built with Z.AI SDK & GLM-5.1

Compatible with Android 15/16

Changelog

    +
  • + v1.3.1 + 2026-05-19 +
      +
    • Fixed: AI actions now AUTO-EXECUTE — files deploy, APKs build & install automatically
    • +
    • Added Keep Screen On toggle — prevents sleep while agent works
    • +
    • Added Auto-Deploy toggle — controls whether AI actions auto-execute
    • +
    • Status toasts show deploy/build/install progress
    • +
    • New settings section: Terminal & Agent (Auto-Deploy, Keep Screen On)
    • +
    +
  • v1.3.0 2026-05-19 diff --git a/www/js/app.js b/www/js/app.js index 5d6f0d7..73861e7 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -28,7 +28,9 @@ streamingConvId: null, streamingContent: '', streamingResponseDiv: null, - terminalOpen: false + terminalOpen: false, + keepAwake: false, + autoDeploy: true }; function $(sel) { return document.querySelector(sel); } @@ -46,6 +48,8 @@ state.currentMode = localStorage.getItem(STORAGE_KEY + 'currentMode') || 'chat'; state.theme = localStorage.getItem(STORAGE_KEY + 'theme') || 'dark'; state.terminalOpen = localStorage.getItem(STORAGE_KEY + 'terminalOpen') === 'true'; + state.keepAwake = localStorage.getItem(STORAGE_KEY + 'keepAwake') === 'true'; + state.autoDeploy = localStorage.getItem(STORAGE_KEY + 'autoDeploy') !== 'false'; var convData = localStorage.getItem(STORAGE_KEY + 'conversations'); state.conversations = convData ? JSON.parse(convData) : []; state.activeConversationId = localStorage.getItem(STORAGE_KEY + 'activeConv') || null; @@ -64,6 +68,8 @@ localStorage.setItem(STORAGE_KEY + 'currentMode', state.currentMode); localStorage.setItem(STORAGE_KEY + 'theme', state.theme); localStorage.setItem(STORAGE_KEY + 'terminalOpen', state.terminalOpen.toString()); + localStorage.setItem(STORAGE_KEY + 'keepAwake', state.keepAwake.toString()); + localStorage.setItem(STORAGE_KEY + 'autoDeploy', state.autoDeploy.toString()); localStorage.setItem(STORAGE_KEY + 'conversations', JSON.stringify(state.conversations)); localStorage.setItem(STORAGE_KEY + 'activeConv', state.activeConversationId || ''); } catch(e) { console.error('Save state error:', e); } @@ -458,6 +464,7 @@ state.streamingContent = ''; updateSendButton(); showThinking(); + if (state.keepAwake) setWakeLock(true); var requestBody = null; var responseDiv = null; @@ -517,6 +524,21 @@ updateSendButton(); saveState(); updateTerminalContent(); + if (state.keepAwake) setWakeLock(false); + + if (state.autoDeploy && (state.currentMode === 'coding' || state.currentMode === 'agentic')) { + var finalContent = state.streamingContent || ''; + if (!finalContent && conv && conv.messages.length) { + var last = conv.messages[conv.messages.length - 1]; + if (last && last.role === 'assistant') finalContent = last.content; + } + if (finalContent) { + var autoActions = parseAiActions(finalContent); + if (autoActions.length > 0) { + autoExecuteActions(autoActions, conv); + } + } + } } } @@ -1014,6 +1036,7 @@ var Shell = null; var Installer = null; + var Wake = null; var termState = { history: [], historyIndex: -1, @@ -1032,9 +1055,18 @@ try { Shell = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Shell; Installer = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Installer; + Wake = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Wake; } catch(e) {} if (!Shell) console.warn('Shell plugin not available'); if (!Installer) console.warn('Installer plugin not available'); + if (!Wake) console.warn('Wake plugin not available'); + } + + async function setWakeLock(on) { + if (!Wake) return; + try { + if (on) { await Wake.acquire(); } else { await Wake.release(); } + } catch(e) { console.warn('WakeLock error:', e); } } async function shellExec(command, cwd, stream) { @@ -1333,6 +1365,64 @@ termPrint('--- Deploy complete ---\n', 'info'); } + function showStatusToast(message, type) { + var existing = $('#status-toast'); + if (existing) existing.remove(); + var toast = document.createElement('div'); + toast.id = 'status-toast'; + toast.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);z-index:1000;' + + 'padding:10px 20px;border-radius:20px;font-size:13px;font-weight:600;max-width:90%;' + + 'text-align:center;pointer-events:none;opacity:0;transition:opacity 0.3s;' + + 'background:' + (type === 'success' ? 'var(--success)' : type === 'err' ? 'var(--danger)' : 'var(--accent)') + + ';color:white;box-shadow:0 4px 12px rgba(0,0,0,0.3)'; + toast.textContent = message; + document.body.appendChild(toast); + requestAnimationFrame(function() { toast.style.opacity = '1'; }); + setTimeout(function() { + toast.style.opacity = '0'; + setTimeout(function() { if (toast.parentElement) toast.remove(); }, 300); + }, 3000); + } + + async function autoExecuteActions(actions, conv) { + var hasFiles = actions.some(function(a) { return a.type === 'create_file'; }); + var hasBuild = actions.some(function(a) { return a.type === 'build_apk'; }); + var hasInstall = actions.some(function(a) { return a.type === 'install_apk'; }); + var hasCommands = actions.some(function(a) { return a.type === 'run_command'; }); + + if (!hasFiles && !hasBuild && !hasInstall && !hasCommands) return; + + var fileCount = actions.filter(function(a) { return a.type === 'create_file'; }).length; + if (fileCount > 0) { + showStatusToast('Auto-deploying ' + fileCount + ' file' + (fileCount > 1 ? 's' : '') + '...', 'info'); + await deployActions(actions); + showStatusToast(fileCount + ' file' + (fileCount > 1 ? 's' : '') + ' deployed', 'success'); + } + + if (hasCommands) { + for (var i = 0; i < actions.length; i++) { + if (actions[i].type === 'run_command') { + showStatusToast('Running: ' + actions[i].command.substring(0, 40) + '...', 'info'); + await termQueueCommand(actions[i].command); + } + } + } + + if (hasBuild) { + showStatusToast('Building APK...', 'info'); + await buildFromActions(actions); + } + + if (hasInstall) { + for (var j = 0; j < actions.length; j++) { + if (actions[j].type === 'install_apk') { + showStatusToast('Installing APK...', 'info'); + await installApk(actions[j].path); + } + } + } + } + async function buildFromActions(actions) { showScreen('terminal'); termPrint('\n--- Building APK ---', 'info'); @@ -1720,6 +1810,8 @@ $('#tokens-value').textContent = state.maxTokens; $('#settings-websearch').checked = state.webSearch; $('#settings-streaming').checked = state.streaming; + $('#settings-autodeploy').checked = state.autoDeploy; + $('#settings-keepawake').checked = state.keepAwake; } function saveSettings() { @@ -1864,6 +1956,14 @@ $('#settings-model').addEventListener('change', saveSettings); $('#settings-websearch').addEventListener('change', saveSettings); $('#settings-streaming').addEventListener('change', saveSettings); + $('#settings-autodeploy').addEventListener('change', function() { + state.autoDeploy = this.checked; + saveState(); + }); + $('#settings-keepawake').addEventListener('change', function() { + state.keepAwake = this.checked; + saveState(); + }); $('#theme-toggle-header').addEventListener('click', toggleTheme);