v1.3.1: Auto-execute AI actions, Keep Screen On, status toasts
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:!*~'
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
||||
@@ -9,6 +9,7 @@ public class MainActivity extends BridgeActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
registerPlugin(ShellPlugin.class);
|
||||
registerPlugin(InstallerPlugin.class);
|
||||
registerPlugin(WakePlugin.class);
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
69
android/app/src/main/java/ai/z/chat/WakePlugin.java
Normal file
69
android/app/src/main/java/ai/z/chat/WakePlugin.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package ai.z.chat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.PowerManager;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.getcapacitor.JSObject;
|
||||
import com.getcapacitor.Plugin;
|
||||
import com.getcapacitor.PluginCall;
|
||||
import com.getcapacitor.PluginMethod;
|
||||
import com.getcapacitor.annotation.CapacitorPlugin;
|
||||
|
||||
@CapacitorPlugin(name = "Wake")
|
||||
public class WakePlugin extends Plugin {
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
private boolean isHeld = false;
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
super.load();
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
public void acquire(PluginCall call) {
|
||||
if (isHeld && wakeLock != null) {
|
||||
call.resolve(new JSObject().put("held", true));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -236,6 +236,25 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>Terminal & Agent</h3>
|
||||
<div class="input-group toggle-group">
|
||||
<label>Auto-Deploy Files</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="settings-autodeploy" checked>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<span class="input-hint">AI-generated code auto-deploys to device in Coding/Agentic modes</span>
|
||||
<div class="input-group toggle-group" style="margin-top:12px">
|
||||
<label>Keep Screen On</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="settings-keepawake">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<span class="input-hint">Prevents screen sleep while agent is working</span>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>Appearance</h3>
|
||||
<div class="input-group toggle-group">
|
||||
@@ -253,13 +272,24 @@
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>About</h3>
|
||||
<p class="about-text">Z.AI Chat v1.3.0</p>
|
||||
<p class="about-text">Z.AI Chat v1.3.1</p>
|
||||
<p class="about-text">Built with Z.AI SDK & GLM-5.1</p>
|
||||
<p class="about-text">Compatible with Android 15/16</p>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>Changelog</h3>
|
||||
<ul class="changelog-list">
|
||||
<li>
|
||||
<span class="changelog-version">v1.3.1</span>
|
||||
<span class="changelog-date">2026-05-19</span>
|
||||
<ul>
|
||||
<li>Fixed: AI actions now AUTO-EXECUTE — files deploy, APKs build & install automatically</li>
|
||||
<li>Added Keep Screen On toggle — prevents sleep while agent works</li>
|
||||
<li>Added Auto-Deploy toggle — controls whether AI actions auto-execute</li>
|
||||
<li>Status toasts show deploy/build/install progress</li>
|
||||
<li>New settings section: Terminal & Agent (Auto-Deploy, Keep Screen On)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<span class="changelog-version">v1.3.0</span>
|
||||
<span class="changelog-date">2026-05-19</span>
|
||||
|
||||
102
www/js/app.js
102
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user