v3.2.0: full QA fixes and in-app virtual environment
This commit is contained in:
@@ -69,24 +69,24 @@
|
||||
<div id="chat-screen" class="screen">
|
||||
<div class="chat-header">
|
||||
<div class="header-left">
|
||||
<button id="menu-btn" class="icon-btn">☰</button>
|
||||
<button id="menu-btn" class="icon-btn" aria-label="Open sidebar menu">☰</button>
|
||||
<div class="header-title">
|
||||
<h2 id="conversation-title">New Chat</h2>
|
||||
<span id="current-mode-label" class="mode-label">Chat</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<button id="file-tree-btn" class="icon-btn" title="Project Files" style="display:none">📁</button>
|
||||
<button id="theme-toggle-header" class="theme-toggle-btn" title="Toggle theme">☾</button>
|
||||
<button id="new-chat-btn" class="icon-btn" title="New chat">+</button>
|
||||
<button id="settings-btn" class="icon-btn" title="Settings">⚙</button>
|
||||
<button id="file-tree-btn" class="icon-btn" title="Project Files" aria-label="Open project files" style="display:none">📁</button>
|
||||
<button id="theme-toggle-header" class="theme-toggle-btn" title="Toggle theme" aria-label="Toggle theme">☾</button>
|
||||
<button id="new-chat-btn" class="icon-btn" title="New chat" aria-label="Start new chat">+</button>
|
||||
<button id="settings-btn" class="icon-btn" title="Settings" aria-label="Open settings">⚙</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sidebar" class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h3>Conversations</h3>
|
||||
<button id="sidebar-close" class="icon-btn">×</button>
|
||||
<button id="sidebar-close" class="icon-btn" aria-label="Close sidebar">×</button>
|
||||
</div>
|
||||
<div id="conversation-list" class="conversation-list"></div>
|
||||
<div class="sidebar-footer">
|
||||
@@ -98,7 +98,7 @@
|
||||
<div id="file-tree-panel" class="file-tree-panel">
|
||||
<div class="file-tree-header">
|
||||
<h3>Project Files</h3>
|
||||
<button id="file-tree-close" class="icon-btn">×</button>
|
||||
<button id="file-tree-close" class="icon-btn" aria-label="Close file tree">×</button>
|
||||
</div>
|
||||
<div id="file-tree-body" class="file-tree-body">
|
||||
<div class="ftree-empty">No files yet.<br>AI-generated files appear here.</div>
|
||||
@@ -118,7 +118,7 @@
|
||||
<div class="file-viewer-actions">
|
||||
<button id="file-viewer-edit" class="fv-btn">Edit</button>
|
||||
<button id="file-viewer-save" class="fv-btn fv-btn-save" style="display:none">Save</button>
|
||||
<button id="file-viewer-close" class="icon-btn">×</button>
|
||||
<button id="file-viewer-close" class="icon-btn" aria-label="Close file viewer">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="file-viewer-body" class="file-viewer-body">
|
||||
@@ -153,10 +153,10 @@
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<textarea id="message-input" placeholder="Type your message..." rows="1"></textarea>
|
||||
<button id="send-btn" class="send-btn" disabled>
|
||||
<button id="send-btn" class="send-btn" aria-label="Send message" disabled>
|
||||
<svg viewBox="0 0 24 24" width="24" height="24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" fill="currentColor"/></svg>
|
||||
</button>
|
||||
<button id="stop-btn" class="stop-btn" style="display:none">
|
||||
<button id="stop-btn" class="stop-btn" aria-label="Stop generation" style="display:none">
|
||||
<svg viewBox="0 0 24 24" width="24" height="24"><rect x="6" y="6" width="12" height="12" fill="currentColor" rx="2"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -166,11 +166,11 @@
|
||||
<div id="terminal-screen" class="screen">
|
||||
<div class="term-screen-container">
|
||||
<div class="term-screen-header">
|
||||
<button id="term-back-btn" class="icon-btn">←</button>
|
||||
<button id="term-back-btn" class="icon-btn" aria-label="Back to chat">←</button>
|
||||
<h2>Terminal</h2>
|
||||
<div class="term-screen-header-right">
|
||||
<span id="term-cwd-display" class="term-cwd-display">~</span>
|
||||
<button id="term-setup-tools-btn" class="icon-btn" title="Setup Dev Tools">🛠</button>
|
||||
<button id="term-setup-tools-btn" class="icon-btn" title="Setup Dev Tools" aria-label="Setup development tools">🛠</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="term-output" class="term-output"></div>
|
||||
@@ -186,8 +186,8 @@
|
||||
<div class="term-input-row">
|
||||
<span class="term-prompt">$</span>
|
||||
<input type="text" id="term-input" class="term-input" placeholder="Enter command..." autocomplete="off" spellcheck="false">
|
||||
<button id="term-run-btn" class="term-run-btn">▶</button>
|
||||
<button id="term-stop-btn" class="term-stop-btn" style="display:none">■</button>
|
||||
<button id="term-run-btn" class="term-run-btn" aria-label="Run terminal command">▶</button>
|
||||
<button id="term-stop-btn" class="term-stop-btn" aria-label="Stop terminal command" style="display:none">■</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -234,7 +234,7 @@
|
||||
<div id="settings-screen" class="screen">
|
||||
<div class="settings-container">
|
||||
<div class="settings-header">
|
||||
<button id="settings-back" class="icon-btn">←</button>
|
||||
<button id="settings-back" class="icon-btn" aria-label="Back to chat">←</button>
|
||||
<h2>Settings</h2>
|
||||
</div>
|
||||
<div class="settings-body">
|
||||
@@ -338,13 +338,25 @@
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>About</h3>
|
||||
<p class="about-text">Z.AI Chat v3.1.0</p>
|
||||
<p class="about-text">Z.AI Chat v3.2.0</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">v3.2.0</span>
|
||||
<span class="changelog-date">2026-05-20</span>
|
||||
<ul>
|
||||
<li><strong>Full In-App Virtual Env</strong> — new internal virtual environment setup with no external Termux app dependency</li>
|
||||
<li><strong>Module Installer</strong> — new AI actions: <code>[VENV_SETUP]</code> and <code>[VENV_PIP_INSTALL package]</code></li>
|
||||
<li><strong>QA Critical Fix</strong> — fixed invalid JS file check that broke PRoot install fallback</li>
|
||||
<li><strong>Stability</strong> — thread-safe process map in ShellPlugin and safer wake lock handling</li>
|
||||
<li><strong>Resource Safety</strong> — fixed high-risk stream/file descriptor leaks in bootstrap extraction and downloads</li>
|
||||
<li><strong>Accessibility</strong> — improved icon button labels for better screen reader support</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<span class="changelog-version">v3.1.0</span>
|
||||
<span class="changelog-date">2026-05-20</span>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
var STORAGE_KEY = 'zai_chat_';
|
||||
var MODE_PROMPTS = {
|
||||
chat: 'You are a helpful, knowledgeable AI assistant. Be concise and accurate.',
|
||||
coding: 'You are an expert coding assistant. Write clean, efficient, well-documented code. Always use markdown code blocks with language tags. Explain your approach briefly before and after code. Handle edge cases and errors properly.',
|
||||
coding: 'You are an expert coding assistant with internal tool access. Use action tags when execution is needed: [CREATE_FILE ...][/CREATE_FILE], [RUN_COMMAND]...[/RUN_COMMAND], [BUILD_APK project], [INSTALL_APK path], [VENV_SETUP], [VENV_PIP_INSTALL package]. Keep responses concise and complete.',
|
||||
brainstorm: 'You are a creative brainstorming partner. Generate diverse ideas, explore unconventional angles, build on concepts, and help evaluate trade-offs. Think freely and expansively. Present ideas in organized lists or tables when appropriate.',
|
||||
agentic: 'You are an autonomous agent with FULL control of an Android device. You have a real terminal, can build APKs, control the device UI via AutoGLM, and use Hermes agent tools.\n\n## File Operations:\n[CREATE_FILE path/to/file.ext]\ncontents\n[/CREATE_FILE]\n\n## Shell:\n[RUN_COMMAND]\ncommand\n[/RUN_COMMAND]\n\n## Build:\n[BUILD_APK project_name]\n[INSTALL_APK /path/to/file.apk]\n\n## AutoGLM Device Control:\n[DEVICE_TAP x y]\n[DEVICE_LONG_PRESS x y]\n[DEVICE_SWIPE startX startY endX endY]\n[DEVICE_TYPE text]\n[DEVICE_PRESS_BACK]\n[DEVICE_PRESS_HOME]\n[DEVICE_PRESS_RECENTS]\n[DEVICE_SCREENSHOT]\n[DEVICE_UI_TREE]\n[DEVICE_CLICK_TEXT button text]\n[DEVICE_CLICK_ID com.example:id/viewId]\n[DEVICE_LAUNCH com.example.app]\n[DEVICE_CURRENT_APP]\n\n## Hermes Agent:\n[HERMES_INSTALL]\n[HERMES_EXEC command]\n\n## Rules:\n1. Use [CREATE_FILE] for files, [BUILD_APK] to compile, [INSTALL_APK] to install\n2. Use [DEVICE_*] for device control — first [DEVICE_UI_TREE] to see screen, then interact\n3. Use [HERMES_EXEC] for Hermes capabilities — web search, terminal, skills, memory\n4. Generate COMPLETE files, never stubs\n5. For Java: package ai.z.app, target SDK 36\n6. Output [TASK_COMPLETE] ONLY when ALL work is done\n7. Never say done unless all files written, builds done, installs done'
|
||||
agentic: 'You are an autonomous agent with full control of this app sandbox: internal Linux bootstrap, terminal, virtual env, build tools, and device UI via AutoGLM.\n\n## File Operations:\n[CREATE_FILE path/to/file.ext]\ncontents\n[/CREATE_FILE]\n\n## Shell:\n[RUN_COMMAND]\ncommand\n[/RUN_COMMAND]\n\n## Build:\n[BUILD_APK project_name]\n[INSTALL_APK /path/to/file.apk]\n\n## In-App Virtual Env:\n[VENV_SETUP]\n[VENV_PIP_INSTALL package_name]\n\n## AutoGLM Device Control:\n[DEVICE_TAP x y]\n[DEVICE_LONG_PRESS x y]\n[DEVICE_SWIPE startX startY endX endY]\n[DEVICE_TYPE text]\n[DEVICE_PRESS_BACK]\n[DEVICE_PRESS_HOME]\n[DEVICE_PRESS_RECENTS]\n[DEVICE_SCREENSHOT]\n[DEVICE_UI_TREE]\n[DEVICE_CLICK_TEXT button text]\n[DEVICE_CLICK_ID com.example:id/viewId]\n[DEVICE_LAUNCH com.example.app]\n[DEVICE_CURRENT_APP]\n\n## Hermes Agent:\n[HERMES_INSTALL]\n[HERMES_EXEC command]\n\n## Rules:\n1. Prefer internal sandbox tools and virtual env first\n2. Use [CREATE_FILE], then [BUILD_APK], then [INSTALL_APK]\n3. Use [DEVICE_*] for device control; start with [DEVICE_UI_TREE]\n4. Generate complete files, never stubs\n5. For Java: package ai.z.app, target SDK 36\n6. Output [TASK_COMPLETE] only when all work is done'
|
||||
};
|
||||
|
||||
var BUILD_SCRIPT = [
|
||||
@@ -623,7 +623,7 @@
|
||||
if (state.streamingContent) {
|
||||
conv.messages.push({ role: 'assistant', content: state.streamingContent, _streaming: false });
|
||||
}
|
||||
var retryDiv = appendRetryMessage(err, requestBody, conv);
|
||||
appendRetryMessage(err, requestBody, conv);
|
||||
} else if (state.streamingContent) {
|
||||
conv.messages.push({ role: 'assistant', content: state.streamingContent, _streaming: false });
|
||||
}
|
||||
@@ -1237,7 +1237,7 @@
|
||||
async function installApk(path) {
|
||||
if (!Installer) { termPrint('[Installer plugin not available]', 'err'); return; }
|
||||
try {
|
||||
var result = await Installer.installApk({ path: path });
|
||||
await Installer.installApk({ path: path });
|
||||
termPrint('[APK install triggered: ' + path + ']', 'success');
|
||||
} catch(e) {
|
||||
termPrint('[Install failed: ' + e.message + ']', 'err');
|
||||
@@ -1441,6 +1441,8 @@
|
||||
var deviceCurrentAppRegex = /\[DEVICE_CURRENT_APP\]/gi;
|
||||
var hermesInstallRegex = /\[HERMES_INSTALL\]/gi;
|
||||
var hermesExecRegex = /\[HERMES_EXEC\s+([^\]]+)\]/gi;
|
||||
var venvSetupRegex = /\[VENV_SETUP\]/gi;
|
||||
var venvPipInstallRegex = /\[VENV_PIP_INSTALL\s+([^\]]+)\]/gi;
|
||||
var codeBlockFileRegex = /```(\w+)\s*\n([\s\S]*?)```/gi;
|
||||
var match;
|
||||
|
||||
@@ -1501,6 +1503,12 @@
|
||||
while ((match = hermesExecRegex.exec(content)) !== null) {
|
||||
actions.push({ type: 'hermes_exec', command: match[1].trim() });
|
||||
}
|
||||
while ((match = venvSetupRegex.exec(content)) !== null) {
|
||||
actions.push({ type: 'venv_setup' });
|
||||
}
|
||||
while ((match = venvPipInstallRegex.exec(content)) !== null) {
|
||||
actions.push({ type: 'venv_pip_install', packages: match[1].trim() });
|
||||
}
|
||||
while ((match = codeBlockFileRegex.exec(content)) !== null) {
|
||||
var lang = match[1];
|
||||
var code = match[2];
|
||||
@@ -1673,7 +1681,8 @@
|
||||
content.indexOf('[BUILD_APK') >= 0 ||
|
||||
content.indexOf('[INSTALL_APK') >= 0 ||
|
||||
content.indexOf('[DEVICE_') >= 0 ||
|
||||
content.indexOf('[HERMES_') >= 0;
|
||||
content.indexOf('[HERMES_') >= 0 ||
|
||||
content.indexOf('[VENV_') >= 0;
|
||||
var hasCodeBlock = content.indexOf('```') >= 0;
|
||||
if (!hasCodeBlock && !hasAction && content.length < 300) return true;
|
||||
if ((content.match(/```/g) || []).length % 2 !== 0) return false;
|
||||
@@ -1895,7 +1904,7 @@
|
||||
var saveBtn = $('#file-viewer-save');
|
||||
var editBtn = $('#file-viewer-edit');
|
||||
|
||||
if (!viewer) return;
|
||||
if (!viewer || !nameEl || !langEl || !contentEl || !textareaEl || !bodyEl || !editorEl || !saveBtn || !editBtn) return;
|
||||
nameEl.textContent = file.path;
|
||||
langEl.textContent = file.language;
|
||||
contentEl.textContent = file.content;
|
||||
@@ -2151,8 +2160,9 @@
|
||||
}
|
||||
|
||||
async function tryProotExec(prootCmd, prefixUsr, pkgBin, aptBin) {
|
||||
var hasPkg = await shellExec('test -f "' + pkgBin + '"', termState.homeDir, false);
|
||||
var pkgCmd = 'sh /usr/bin/pkg update -y 2>&1 && sh /usr/bin/pkg install -y aapt2 ecj dx apksigner 2>&1';
|
||||
if (!new File(pkgBin).exists) pkgCmd = 'sh /usr/bin/apt update -y 2>&1 && sh /usr/bin/apt install -y aapt2 ecj dx apksigner 2>&1';
|
||||
if (hasPkg.exitCode !== 0) pkgCmd = 'sh /usr/bin/apt update -y 2>&1 && sh /usr/bin/apt install -y aapt2 ecj dx apksigner 2>&1';
|
||||
var wrappedCmd = prootCmd + ' -0 -b /dev -b /proc -b /sys -r ' + prefixUsr + ' /bin/sh -c \'' + pkgCmd.replace(/'/g, "'\\''") + '\'';
|
||||
|
||||
termPrint('[*] Running pkg via PRoot...', 'info');
|
||||
@@ -2186,7 +2196,7 @@
|
||||
|
||||
termPrint('[OK] Termux detected! Sending install command...', 'success');
|
||||
try {
|
||||
var runResult = await Bootstrap.runInTermux({command: 'pkg update -y && pkg install -y aapt2 ecj dx apksigner'});
|
||||
await Bootstrap.runInTermux({command: 'pkg update -y && pkg install -y aapt2 ecj dx apksigner'});
|
||||
termPrint('[*] Command sent to Termux. Waiting...', 'info');
|
||||
await new Promise(function(r) { setTimeout(r, 15000); });
|
||||
if (await toolsReady()) return true;
|
||||
@@ -2314,8 +2324,9 @@
|
||||
var hasCommands = actions.some(function(a) { return a.type === 'run_command'; });
|
||||
var hasDevice = actions.some(function(a) { return a.type && a.type.indexOf('device_') === 0; });
|
||||
var hasHermes = actions.some(function(a) { return a.type && a.type.indexOf('hermes_') === 0; });
|
||||
var hasVenv = actions.some(function(a) { return a.type && a.type.indexOf('venv_') === 0; });
|
||||
|
||||
if (!hasFiles && !hasBuild && !hasInstall && !hasCommands && !hasDevice && !hasHermes) return;
|
||||
if (!hasFiles && !hasBuild && !hasInstall && !hasCommands && !hasDevice && !hasHermes && !hasVenv) return;
|
||||
|
||||
_agenticRetryCount = 0;
|
||||
var resultLog = [];
|
||||
@@ -2376,6 +2387,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (hasVenv) {
|
||||
for (var v = 0; v < actions.length; v++) {
|
||||
var vAct = actions[v];
|
||||
if (vAct.type.indexOf('venv_') !== 0) continue;
|
||||
try {
|
||||
var venvResult = await executeVirtualEnvAction(vAct);
|
||||
resultLog.push(venvResult);
|
||||
termPrint(venvResult, '');
|
||||
} catch(e) {
|
||||
resultLog.push('VENV_ERROR: ' + e.message);
|
||||
termPrint('[!] Venv: ' + e.message, 'err');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasBuild) {
|
||||
showStatusToast('Building APK...', 'info');
|
||||
var toolsReady = await ensureBuildTools();
|
||||
@@ -2485,6 +2511,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function executeVirtualEnvAction(action) {
|
||||
var bsPlugin = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Bootstrap;
|
||||
if (!bsPlugin) throw new Error('Bootstrap plugin not available');
|
||||
var venvPath = termState.homeDir ? (termState.homeDir.replace(/\/home$/, '') + '/venv/default') : '';
|
||||
|
||||
switch (action.type) {
|
||||
case 'venv_setup': {
|
||||
showStatusToast('Setting up in-app virtual environment...', 'info');
|
||||
var setup = await bsPlugin.setupVirtualEnv({ venv: venvPath });
|
||||
termState.venvPath = setup.venv;
|
||||
return '[VENV] Ready: ' + setup.venv;
|
||||
}
|
||||
case 'venv_pip_install': {
|
||||
if (!termState.venvPath) {
|
||||
var init = await bsPlugin.setupVirtualEnv({ venv: venvPath });
|
||||
termState.venvPath = init.venv;
|
||||
}
|
||||
showStatusToast('Installing module(s): ' + action.packages, 'info');
|
||||
var installed = await bsPlugin.venvPipInstall({ venv: termState.venvPath, packages: action.packages });
|
||||
return '[VENV] pip install ' + action.packages + '\n' + (installed.output || '').substring(0, 1200);
|
||||
}
|
||||
default:
|
||||
return '[VENV] Unknown action: ' + action.type;
|
||||
}
|
||||
}
|
||||
|
||||
async function autoDeployFile(action) {
|
||||
var path = action.path;
|
||||
if (!path.startsWith('/')) {
|
||||
@@ -2716,7 +2768,7 @@
|
||||
});
|
||||
|
||||
try {
|
||||
var result = await Bootstrap.install();
|
||||
await Bootstrap.install();
|
||||
|
||||
progressText.textContent = 'Fixing file permissions...';
|
||||
try { await Bootstrap.fixPermissions(); } catch(e) {}
|
||||
|
||||
Reference in New Issue
Block a user