create-openclaw-bot 5.8.21 → 5.8.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -16
- package/README.vi.md +14 -16
- package/dist/cli.js +12 -8
- package/dist/server/local-server.js +511 -461
- package/dist/setup/shared/bot-config-gen.js +41 -8
- package/dist/setup/shared/docker-gen.js +11 -9
- package/dist/web/app.js +73 -72
- package/package.json +1 -1
|
@@ -91,11 +91,11 @@
|
|
|
91
91
|
}));
|
|
92
92
|
|
|
93
93
|
const cfg = {
|
|
94
|
-
meta: {
|
|
95
|
-
lastTouchedVersion: (_common.OPENCLAW_NPM_SPEC || 'latest').replace('openclaw@', ''),
|
|
96
|
-
osChoice,
|
|
97
|
-
deployMode,
|
|
98
|
-
},
|
|
94
|
+
meta: {
|
|
95
|
+
lastTouchedVersion: (_common.OPENCLAW_NPM_SPEC || 'latest').replace('openclaw@', ''),
|
|
96
|
+
osChoice,
|
|
97
|
+
deployMode,
|
|
98
|
+
},
|
|
99
99
|
agents: {
|
|
100
100
|
defaults: {
|
|
101
101
|
model: { primary: model, fallbacks: [] },
|
|
@@ -165,8 +165,31 @@
|
|
|
165
165
|
|
|
166
166
|
// ── tools ────────────────────────────────────────────────────────────────
|
|
167
167
|
cfg.tools = { profile: 'full', exec: { host: 'gateway', security: 'full', ask: 'off' } };
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
const alsoAllow = [];
|
|
169
|
+
if (selectedSkills.includes('scheduler') || selectedSkills.includes('cron')) {
|
|
170
|
+
alsoAllow.push('group:automation');
|
|
171
|
+
}
|
|
172
|
+
if (
|
|
173
|
+
selectedSkills.includes('web-search') ||
|
|
174
|
+
selectedSkills.includes('web_search') ||
|
|
175
|
+
selectedSkills.includes('browser') ||
|
|
176
|
+
selectedSkills.includes('browser-automation') ||
|
|
177
|
+
hasBrowserDesktop ||
|
|
178
|
+
hasBrowserServer
|
|
179
|
+
) {
|
|
180
|
+
alsoAllow.push('group:web');
|
|
181
|
+
}
|
|
182
|
+
if (alsoAllow.length > 0) {
|
|
183
|
+
cfg.tools.alsoAllow = alsoAllow;
|
|
184
|
+
}
|
|
185
|
+
// DuckDuckGo is the bundled, credential-free web_search provider. Auto-detect only
|
|
186
|
+
// picks providers that have credentials, so a free provider must be selected
|
|
187
|
+
// explicitly, otherwise web_search reports "no provider is available".
|
|
188
|
+
if (selectedSkills.includes('web-search') || selectedSkills.includes('web_search')) {
|
|
189
|
+
cfg.tools.web = {
|
|
190
|
+
...(cfg.tools.web || {}),
|
|
191
|
+
search: { ...((cfg.tools.web && cfg.tools.web.search) || {}), provider: 'duckduckgo' },
|
|
192
|
+
};
|
|
170
193
|
}
|
|
171
194
|
if (isMultiBot) {
|
|
172
195
|
cfg.tools.agentToAgent = {
|
|
@@ -203,6 +226,8 @@
|
|
|
203
226
|
selectedSkills,
|
|
204
227
|
botName: agentMetas[0]?.name || 'Bot',
|
|
205
228
|
agentId: agentMetas[0]?.agentId || 'bot',
|
|
229
|
+
hasBrowser: hasBrowserDesktop || hasBrowserServer
|
|
230
|
+
|| selectedSkills.includes('browser') || selectedSkills.includes('browser-automation'),
|
|
206
231
|
});
|
|
207
232
|
cfg.plugins = pluginsConfig.plugins;
|
|
208
233
|
|
|
@@ -298,7 +323,7 @@
|
|
|
298
323
|
// buildPluginsConfig — returns { plugins: { ... } }
|
|
299
324
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
300
325
|
function buildPluginsConfig(opts) {
|
|
301
|
-
const { channelKey, selectedSkills = [], botName = 'Bot', agentId = 'bot' } = opts;
|
|
326
|
+
const { channelKey, selectedSkills = [], botName = 'Bot', agentId = 'bot', hasBrowser = false } = opts;
|
|
302
327
|
|
|
303
328
|
const entries = {};
|
|
304
329
|
|
|
@@ -326,8 +351,16 @@
|
|
|
326
351
|
}
|
|
327
352
|
}
|
|
328
353
|
|
|
354
|
+
// Browser automation depends on the bundled `browser` plugin, which provides the
|
|
355
|
+
// browser-control service. With an allowlist in use it must be explicitly allowed,
|
|
356
|
+
// otherwise browser control stays disabled ("browser control is disabled").
|
|
357
|
+
if (hasBrowser && !allow.includes('browser')) {
|
|
358
|
+
allow.push('browser');
|
|
359
|
+
}
|
|
360
|
+
|
|
329
361
|
const plugins = { entries };
|
|
330
362
|
plugins.allow = allow;
|
|
363
|
+
if (allow.length) plugins.bundledDiscovery = 'compat';
|
|
331
364
|
|
|
332
365
|
return { plugins };
|
|
333
366
|
}
|
|
@@ -185,7 +185,7 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
185
185
|
const port = routerPort || 20128;
|
|
186
186
|
const nineRouterSpec = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon && globalThis.__openclawCommon.NINE_ROUTER_NPM_SPEC) || '9router@latest';
|
|
187
187
|
return [
|
|
188
|
-
`npm install -g ` + nineRouterSpec
|
|
188
|
+
`npm install -g ` + nineRouterSpec,
|
|
189
189
|
'node /tmp/patch-9router.js || true',
|
|
190
190
|
'node -e "const fs=require(\'fs\'),path=require(\'path\'); const DB_PATH=\'/root/.9router/db/data.sqlite\'; const dir=path.dirname(DB_PATH); if(!fs.existsSync(dir))fs.mkdirSync(dir,{recursive:true}); try{ const {DatabaseSync}=require(\'node:sqlite\'); const db=new DatabaseSync(DB_PATH); db.prepare(\'CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL)\').run(); const existing=db.prepare(\'SELECT * FROM settings WHERE id = 1\').get(); if(!existing){ db.prepare(\'INSERT INTO settings (id, data) VALUES (1, ?)\').run(JSON.stringify({requireLogin:false})); } db.close(); }catch(e){}" || true',
|
|
191
191
|
'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
|
|
@@ -221,12 +221,12 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
221
221
|
singleOllamaContainerName = 'ollama',
|
|
222
222
|
multiOllamaContainerName = 'ollama-multibot',
|
|
223
223
|
plainSingleExtraHosts = false,
|
|
224
|
-
multiOllamaNumParallel = 1,
|
|
225
|
-
singleOllamaNumParallel = 1,
|
|
226
|
-
gatewayPort = 18789,
|
|
227
|
-
routerPort = 20128,
|
|
228
|
-
osChoice = '',
|
|
229
|
-
} = options;
|
|
224
|
+
multiOllamaNumParallel = 1,
|
|
225
|
+
singleOllamaNumParallel = 1,
|
|
226
|
+
gatewayPort = 18789,
|
|
227
|
+
routerPort = 20128,
|
|
228
|
+
osChoice = '',
|
|
229
|
+
} = options;
|
|
230
230
|
const skillLines = dockerfileSkillInstallMode === 'build' && allSkills.length > 0
|
|
231
231
|
? `\n# Install skills (ClawHub)\n${allSkills.map((skill) => `RUN openclaw skills install ${skill} || echo "Warning: Failed to install ${skill} due to rate limits."`).join('\n')}\n`
|
|
232
232
|
: '';
|
|
@@ -356,7 +356,7 @@ CMD ["/bin/sh", "/usr/local/bin/openclaw-entrypoint.sh"]`;
|
|
|
356
356
|
const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(routerPort);
|
|
357
357
|
const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
|
|
358
358
|
|
|
359
|
-
const appEnvironmentBlock = ` environment:\n - HOME=/home/node/project/.openclaw\n - OPENCLAW_HOME=/home/node/project/.openclaw\n - OPENCLAW_STATE_DIR=/home/node/project/.openclaw\n - OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1\n - OPENCLAW_SETUP_OS=${osChoice || ''}\n - OPENCLAW_BROWSER_HOST_OS=${osChoice || ''}\n - OPENCLAW_GATEWAY_PORT=${gatewayPort}\n - OPENCLAW_PORT=${gatewayPort}\n tmpfs:\n - /home/node/project/.openclaw/plugin-runtime-deps\n`;
|
|
359
|
+
const appEnvironmentBlock = ` environment:\n - HOME=/home/node/project/.openclaw\n - OPENCLAW_HOME=/home/node/project/.openclaw\n - OPENCLAW_STATE_DIR=/home/node/project/.openclaw\n - OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1\n - OPENCLAW_SETUP_OS=${osChoice || ''}\n - OPENCLAW_BROWSER_HOST_OS=${osChoice || ''}\n - OPENCLAW_GATEWAY_PORT=${gatewayPort}\n - OPENCLAW_PORT=${gatewayPort}\n tmpfs:\n - /home/node/project/.openclaw/plugin-runtime-deps\n`;
|
|
360
360
|
|
|
361
361
|
let compose;
|
|
362
362
|
if (isMultiBot) {
|
|
@@ -471,6 +471,7 @@ services:
|
|
|
471
471
|
${appEnvironmentBlock}${extraHostsBlock}\n volumes:
|
|
472
472
|
- ${volumeMount}
|
|
473
473
|
- openclaw-plugins:/home/node/project/.openclaw/npm
|
|
474
|
+
- openclaw-extensions:/home/node/project/.openclaw/extensions
|
|
474
475
|
ports:
|
|
475
476
|
- "${gatewayPort}:${gatewayPort}"
|
|
476
477
|
|
|
@@ -496,7 +497,8 @@ ${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
|
496
497
|
|
|
497
498
|
volumes:
|
|
498
499
|
9router-data:
|
|
499
|
-
openclaw-plugins
|
|
500
|
+
openclaw-plugins:
|
|
501
|
+
openclaw-extensions:`;
|
|
500
502
|
} else if (isLocal) {
|
|
501
503
|
const ollamaModelTag = String(selectedModel || 'ollama/gemma4:e2b').replace('ollama/', '');
|
|
502
504
|
compose = `name: ${singleComposeName}
|
package/dist/web/app.js
CHANGED
|
@@ -31,25 +31,25 @@ function staticChoiceCard(item) {
|
|
|
31
31
|
</div>`;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
async function api(path, opts) {
|
|
35
|
-
const res = await fetch(path, opts && opts.body ? { ...opts, headers: { 'content-type': 'application/json' }, body: JSON.stringify(opts.body) } : opts);
|
|
36
|
-
if (!res.ok) throw new Error((await res.json()).error || res.statusText);
|
|
37
|
-
return res.json();
|
|
38
|
-
}
|
|
39
|
-
function activeProjectDir() {
|
|
40
|
-
return state.selectedProjectDir || state.install?.projectDir || '';
|
|
41
|
-
}
|
|
42
|
-
function projectQuery(extra = {}) {
|
|
43
|
-
const params = new URLSearchParams();
|
|
44
|
-
const projectDir = activeProjectDir();
|
|
45
|
-
if (projectDir) params.set('projectDir', projectDir);
|
|
46
|
-
Object.entries(extra).forEach(([key, value]) => {
|
|
47
|
-
if (value !== undefined && value !== null && value !== '') params.set(key, value);
|
|
48
|
-
});
|
|
49
|
-
const query = params.toString();
|
|
50
|
-
return query ? `?${query}` : '';
|
|
51
|
-
}
|
|
52
|
-
function runtimeBadge(label, kind='') { return `<span class="mini-pill ${kind?`mini-pill--${kind}`:''}">${escapeHtml(label)}</span>`; }
|
|
34
|
+
async function api(path, opts) {
|
|
35
|
+
const res = await fetch(path, opts && opts.body ? { ...opts, headers: { 'content-type': 'application/json' }, body: JSON.stringify(opts.body) } : opts);
|
|
36
|
+
if (!res.ok) throw new Error((await res.json()).error || res.statusText);
|
|
37
|
+
return res.json();
|
|
38
|
+
}
|
|
39
|
+
function activeProjectDir() {
|
|
40
|
+
return state.selectedProjectDir || state.install?.projectDir || '';
|
|
41
|
+
}
|
|
42
|
+
function projectQuery(extra = {}) {
|
|
43
|
+
const params = new URLSearchParams();
|
|
44
|
+
const projectDir = activeProjectDir();
|
|
45
|
+
if (projectDir) params.set('projectDir', projectDir);
|
|
46
|
+
Object.entries(extra).forEach(([key, value]) => {
|
|
47
|
+
if (value !== undefined && value !== null && value !== '') params.set(key, value);
|
|
48
|
+
});
|
|
49
|
+
const query = params.toString();
|
|
50
|
+
return query ? `?${query}` : '';
|
|
51
|
+
}
|
|
52
|
+
function runtimeBadge(label, kind='') { return `<span class="mini-pill ${kind?`mini-pill--${kind}`:''}">${escapeHtml(label)}</span>`; }
|
|
53
53
|
async function withButtonLoading(btn, task) {
|
|
54
54
|
if (!btn || btn.classList.contains('is-loading')) return;
|
|
55
55
|
const prevDisabled = btn.disabled;
|
|
@@ -433,7 +433,7 @@ function renderFilesPanel() {
|
|
|
433
433
|
// Re-wire only file-panel-specific handlers
|
|
434
434
|
panel.querySelectorAll('[data-select-file]').forEach(btn => btn.onclick = () => { state.selectedFile = btn.dataset.selectFile; renderFilesPanel(); });
|
|
435
435
|
panel.querySelectorAll('[data-toggle-dir]').forEach(btn => btn.onclick = () => { const p = btn.dataset.toggleDir; state.openDirs[p] = !(state.openDirs[p] ?? true); renderFilesPanel(); });
|
|
436
|
-
panel.querySelectorAll('.save').forEach(btn => btn.onclick = () => withButtonLoading(btn, async () => { const name = btn.dataset.file; await api('/api/bot/files/'+encodeURIComponent(name), { method: 'PUT', body: { projectDir: activeProjectDir(), content: document.querySelector(`[data-editor="${CSS.escape(name)}"]`).value } }); showToast(t('Đã lưu', 'Saved'), t('Đã lưu tệp tin: ', 'Saved file: ') + fileBaseName(name), 'success'); btn.innerHTML=`${actionIcon('save')} ${ui('saved')}`; setTimeout(()=>btn.innerHTML=`${actionIcon('save')} ${ui('save')}`,1200); }));
|
|
436
|
+
panel.querySelectorAll('.save').forEach(btn => btn.onclick = () => withButtonLoading(btn, async () => { const name = btn.dataset.file; await api('/api/bot/files/'+encodeURIComponent(name), { method: 'PUT', body: { projectDir: activeProjectDir(), content: document.querySelector(`[data-editor="${CSS.escape(name)}"]`).value } }); showToast(t('Đã lưu', 'Saved'), t('Đã lưu tệp tin: ', 'Saved file: ') + fileBaseName(name), 'success'); btn.innerHTML=`${actionIcon('save')} ${ui('saved')}`; setTimeout(()=>btn.innerHTML=`${actionIcon('save')} ${ui('save')}`,1200); }));
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
function renderSkillsPanel() {
|
|
@@ -452,7 +452,7 @@ function wireSkillsHandlers(scope = document) {
|
|
|
452
452
|
state.featureLoading[key] = true;
|
|
453
453
|
renderSkillsPanel();
|
|
454
454
|
try {
|
|
455
|
-
await api('/api/features/toggle', { method:'POST', body:{ projectDir: activeProjectDir(), kind, id, enabled, agentId: currentBotId() } });
|
|
455
|
+
await api('/api/features/toggle', { method:'POST', body:{ projectDir: activeProjectDir(), kind, id, enabled, agentId: currentBotId() } });
|
|
456
456
|
await loadFeatureFlags(true);
|
|
457
457
|
if (kind === 'skill') await loadFiles(true);
|
|
458
458
|
} finally {
|
|
@@ -466,7 +466,7 @@ function wireSkillsHandlers(scope = document) {
|
|
|
466
466
|
state.featureLoading[key] = true;
|
|
467
467
|
renderSkillsPanel();
|
|
468
468
|
try {
|
|
469
|
-
await api('/api/features/install', { method:'POST', body:{ projectDir: activeProjectDir(), kind, id, agentId: currentBotId() } });
|
|
469
|
+
await api('/api/features/install', { method:'POST', body:{ projectDir: activeProjectDir(), kind, id, agentId: currentBotId() } });
|
|
470
470
|
await loadFeatureFlags(true);
|
|
471
471
|
await loadFiles(true);
|
|
472
472
|
const label = kind === 'skill' ? t('kỹ năng: ', 'skill: ') : t('plugin: ', 'plugin: ');
|
|
@@ -855,6 +855,7 @@ function botSkillsPanel() {
|
|
|
855
855
|
{ id: 'image-gen', title: 'Tạo ảnh Infographic', desc: 'Tạo ảnh infographic, poster tự động qua 9Router' },
|
|
856
856
|
{ id: 'web-search', title: 'Web Search', desc: 'Tìm kiếm web thời gian thực (DuckDuckGo)' },
|
|
857
857
|
{ id: 'sticker-mention', title: 'Sticker & Auto-Tag (Zalo)', desc: 'Tự động tag người gửi và gửi sticker Zalo theo từ khóa' },
|
|
858
|
+
{ id: 'learning-memory', title: 'Siêu Trí Nhớ Dài Hạn (learning-memory)', desc: 'Tự động ghi nhớ bài học vào MEMORY.md, tự đóng gói và tiến hóa kỹ năng mới vào skills/' },
|
|
858
859
|
];
|
|
859
860
|
const plugins = [
|
|
860
861
|
{ id: 'openclaw-browser-automation', title: 'openclaw-browser-automation', desc: 'Smart Search + Browser (headless & Chrome thật)' },
|
|
@@ -867,7 +868,7 @@ function botSkillsPanel() {
|
|
|
867
868
|
const row = (item, group) => {
|
|
868
869
|
const key = `${group}:${item.id}`;
|
|
869
870
|
const loading = !!state.featureLoading[key];
|
|
870
|
-
const requiresInstall = group === 'plugin' || (group === 'skill' && (item.id === 'image-gen' || item.id === 'sticker-mention'));
|
|
871
|
+
const requiresInstall = group === 'plugin' || (group === 'skill' && (item.id === 'image-gen' || item.id === 'sticker-mention' || item.id === 'learning-memory'));
|
|
871
872
|
const isInstalled = !requiresInstall || !!state.featureInstalled?.[key];
|
|
872
873
|
|
|
873
874
|
let actionsHtml = '';
|
|
@@ -922,7 +923,7 @@ function wireTab() {
|
|
|
922
923
|
state.zaloLoginLines = [t('Đang chuẩn bị quét mã Zalo...', 'Preparing Zalo QR login...')];
|
|
923
924
|
render();
|
|
924
925
|
try {
|
|
925
|
-
await api('/api/zalo/login', { method: 'POST', body: { projectDir: activeProjectDir(), agentId: state.activeBotId } });
|
|
926
|
+
await api('/api/zalo/login', { method: 'POST', body: { projectDir: activeProjectDir(), agentId: state.activeBotId } });
|
|
926
927
|
} catch (err) {
|
|
927
928
|
state.zaloLoginOpen = false;
|
|
928
929
|
state.confirmModal = { title: t('Lỗi đăng nhập','Login error'), message: err.message, okText: t('Đóng','Close'), onConfirm: () => {} };
|
|
@@ -1029,12 +1030,12 @@ document.querySelectorAll('[data-project-pick-folder]').forEach(btn => btn.oncli
|
|
|
1029
1030
|
}
|
|
1030
1031
|
}));
|
|
1031
1032
|
document.querySelectorAll('[data-update-app]').forEach(btn => btn.onclick = () => withButtonLoading(btn, async () => {
|
|
1032
|
-
await api('/api/runtime/update', { method: 'POST', body: { projectDir: activeProjectDir(), target: 'openclaw' } });
|
|
1033
|
+
await api('/api/runtime/update', { method: 'POST', body: { projectDir: activeProjectDir(), target: 'openclaw' } });
|
|
1033
1034
|
await loadSystem();
|
|
1034
1035
|
await loadStatus();
|
|
1035
1036
|
}));
|
|
1036
1037
|
document.querySelectorAll('[data-update-router]').forEach(btn => btn.onclick = () => withButtonLoading(btn, async () => {
|
|
1037
|
-
await api('/api/runtime/update', { method: 'POST', body: { projectDir: activeProjectDir(), target: '9router' } });
|
|
1038
|
+
await api('/api/runtime/update', { method: 'POST', body: { projectDir: activeProjectDir(), target: '9router' } });
|
|
1038
1039
|
await loadSystem();
|
|
1039
1040
|
await loadStatus();
|
|
1040
1041
|
}));document.querySelectorAll('[data-project-remove]').forEach(btn => btn.onclick = (ev) => {
|
|
@@ -1083,7 +1084,7 @@ document.querySelectorAll('[data-project-pick-folder]').forEach(btn => btn.oncli
|
|
|
1083
1084
|
okText: t('Xóa bot','Delete bot'),
|
|
1084
1085
|
onConfirm: async () => {
|
|
1085
1086
|
try {
|
|
1086
|
-
await api('/api/bot/'+encodeURIComponent(id) + projectQuery(), { method: 'DELETE' });
|
|
1087
|
+
await api('/api/bot/'+encodeURIComponent(id) + projectQuery(), { method: 'DELETE' });
|
|
1087
1088
|
state.confirmModal = null;
|
|
1088
1089
|
showToast(t('Đã xóa bot', 'Bot deleted'), `${t('Đã xóa bot','Bot deleted')}: ${id}`, 'success');
|
|
1089
1090
|
if (state.activeBotId === id) { state.activeBotId = ''; state.selectedFile = ''; state.files = []; }
|
|
@@ -1119,7 +1120,7 @@ document.querySelectorAll('[data-project-pick-folder]').forEach(btn => btn.oncli
|
|
|
1119
1120
|
const submitBtn = ev.currentTarget.querySelector('button[type="submit"]');
|
|
1120
1121
|
withButtonLoading(submitBtn, async () => {
|
|
1121
1122
|
const nineRouterApiKey = ev.currentTarget.querySelector('[name="nineRouterApiKey"]')?.value || '';
|
|
1122
|
-
await api('/api/bot/credentials', { method: 'PUT', body: { projectDir: activeProjectDir(), nineRouterApiKey } });
|
|
1123
|
+
await api('/api/bot/credentials', { method: 'PUT', body: { projectDir: activeProjectDir(), nineRouterApiKey } });
|
|
1123
1124
|
const msg = ev.currentTarget.querySelector('[data-cred-msg]');
|
|
1124
1125
|
if (msg) msg.textContent = t('Đã lưu','Saved');
|
|
1125
1126
|
await loadStatus();
|
|
@@ -1194,10 +1195,10 @@ document.querySelectorAll('[data-project-pick-folder]').forEach(btn => btn.oncli
|
|
|
1194
1195
|
const submitBtn = ev.currentTarget.querySelector('button[type="submit"]');
|
|
1195
1196
|
if (submitBtn?.classList.contains('is-loading')) return;
|
|
1196
1197
|
await withButtonLoading(submitBtn, async () => {
|
|
1197
|
-
const fd = new FormData(ev.currentTarget);
|
|
1198
|
-
const body = Object.fromEntries(fd.entries());
|
|
1199
|
-
body.projectDir = activeProjectDir();
|
|
1200
|
-
body.channel = state.botChannel || 'telegram';
|
|
1198
|
+
const fd = new FormData(ev.currentTarget);
|
|
1199
|
+
const body = Object.fromEntries(fd.entries());
|
|
1200
|
+
body.projectDir = activeProjectDir();
|
|
1201
|
+
body.channel = state.botChannel || 'telegram';
|
|
1201
1202
|
if (body.channel === 'zalo-personal') {
|
|
1202
1203
|
state.botModalOpen = false;
|
|
1203
1204
|
state.zaloLoginOpen = true;
|
|
@@ -1241,21 +1242,21 @@ document.querySelectorAll('[data-project-pick-folder]').forEach(btn => btn.oncli
|
|
|
1241
1242
|
}
|
|
1242
1243
|
});
|
|
1243
1244
|
});
|
|
1244
|
-
document.querySelectorAll('.save').forEach(btn => btn.onclick = () => withButtonLoading(btn, async () => { const name = btn.dataset.file; await api('/api/bot/files/'+encodeURIComponent(name), { method: 'PUT', body: { projectDir: activeProjectDir(), content: document.querySelector(`[data-editor="${CSS.escape(name)}"]`).value } }); showToast(t('Đã lưu', 'Saved'), t('Đã lưu tệp tin: ', 'Saved file: ') + fileBaseName(name), 'success'); btn.innerHTML=`${actionIcon('save')} ${ui('saved')}`; setTimeout(()=>btn.innerHTML=`${actionIcon('save')} ${ui('save')}`,1200); }));
|
|
1245
|
+
document.querySelectorAll('.save').forEach(btn => btn.onclick = () => withButtonLoading(btn, async () => { const name = btn.dataset.file; await api('/api/bot/files/'+encodeURIComponent(name), { method: 'PUT', body: { projectDir: activeProjectDir(), content: document.querySelector(`[data-editor="${CSS.escape(name)}"]`).value } }); showToast(t('Đã lưu', 'Saved'), t('Đã lưu tệp tin: ', 'Saved file: ') + fileBaseName(name), 'success'); btn.innerHTML=`${actionIcon('save')} ${ui('saved')}`; setTimeout(()=>btn.innerHTML=`${actionIcon('save')} ${ui('save')}`,1200); }));
|
|
1245
1246
|
wireSkillsHandlers(document);
|
|
1246
1247
|
}
|
|
1247
1248
|
|
|
1248
1249
|
function escapeHtml(s='') { return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
|
|
1249
1250
|
function fileBaseName(s='') { const clean = String(s).replace(/[\\/]+$/, ''); return clean.split(/[\\/]/).pop() || s; }
|
|
1250
|
-
async function loadSystem(silent=false){ state.system = await api('/api/system'); if (!silent) render(); }
|
|
1251
|
-
async function loadStatus(silent=false){
|
|
1252
|
-
const requestedProjectDir = activeProjectDir();
|
|
1253
|
-
const install = await api('/api/bot/status' + projectQuery());
|
|
1254
|
-
if (requestedProjectDir && install?.projectDir && install.projectDir !== requestedProjectDir) return;
|
|
1255
|
-
state.install = install;
|
|
1256
|
-
if (!state.selectedProjectDir && state.install?.projectDir) state.selectedProjectDir = state.install.projectDir;
|
|
1257
|
-
if (!silent) render();
|
|
1258
|
-
}
|
|
1251
|
+
async function loadSystem(silent=false){ state.system = await api('/api/system'); if (!silent) render(); }
|
|
1252
|
+
async function loadStatus(silent=false){
|
|
1253
|
+
const requestedProjectDir = activeProjectDir();
|
|
1254
|
+
const install = await api('/api/bot/status' + projectQuery());
|
|
1255
|
+
if (requestedProjectDir && install?.projectDir && install.projectDir !== requestedProjectDir) return;
|
|
1256
|
+
state.install = install;
|
|
1257
|
+
if (!state.selectedProjectDir && state.install?.projectDir) state.selectedProjectDir = state.install.projectDir;
|
|
1258
|
+
if (!silent) render();
|
|
1259
|
+
}
|
|
1259
1260
|
function autoSwitchBotChannel() {
|
|
1260
1261
|
const bots = state.install?.bots || [];
|
|
1261
1262
|
const ch = state.botChannel || 'telegram';
|
|
@@ -1276,32 +1277,32 @@ function currentBotId() {
|
|
|
1276
1277
|
}
|
|
1277
1278
|
return state.activeBotId || '';
|
|
1278
1279
|
}
|
|
1279
|
-
async function loadFiles(silent=false){
|
|
1280
|
-
const botId = currentBotId();
|
|
1281
|
-
if (!botId) { state.files = []; if (!silent) render(); return; }
|
|
1282
|
-
try {
|
|
1283
|
-
state.files = (await api('/api/bot/files' + projectQuery({ agentId: botId }))).files;
|
|
1284
|
-
} catch (_) {
|
|
1285
|
-
state.files = [];
|
|
1286
|
-
}
|
|
1287
|
-
if (!silent) render();
|
|
1288
|
-
}
|
|
1289
|
-
async function loadCatalog(silent=false){ state.catalog = await api('/api/catalog'); if (!silent) render(); }
|
|
1290
|
-
async function loadFeatureFlags(silent=false){
|
|
1291
|
-
const botId=currentBotId();
|
|
1292
|
-
if (!activeProjectDir()) { state.featureFlags = {}; state.featureInstalled = {}; state.featureVersions = {}; if (!silent) render(); return; }
|
|
1293
|
-
try {
|
|
1294
|
-
const data = (await api('/api/features' + projectQuery({ agentId: botId }))) || {};
|
|
1295
|
-
state.featureFlags = data.flags || {};
|
|
1296
|
-
state.featureInstalled = data.installed || {};
|
|
1297
|
-
state.featureVersions = data.versions || {};
|
|
1298
|
-
} catch (_) {
|
|
1299
|
-
state.featureFlags = {};
|
|
1300
|
-
state.featureInstalled = {};
|
|
1301
|
-
state.featureVersions = {};
|
|
1302
|
-
}
|
|
1303
|
-
if (!silent) render();
|
|
1304
|
-
}
|
|
1280
|
+
async function loadFiles(silent=false){
|
|
1281
|
+
const botId = currentBotId();
|
|
1282
|
+
if (!botId) { state.files = []; if (!silent) render(); return; }
|
|
1283
|
+
try {
|
|
1284
|
+
state.files = (await api('/api/bot/files' + projectQuery({ agentId: botId }))).files;
|
|
1285
|
+
} catch (_) {
|
|
1286
|
+
state.files = [];
|
|
1287
|
+
}
|
|
1288
|
+
if (!silent) render();
|
|
1289
|
+
}
|
|
1290
|
+
async function loadCatalog(silent=false){ state.catalog = await api('/api/catalog'); if (!silent) render(); }
|
|
1291
|
+
async function loadFeatureFlags(silent=false){
|
|
1292
|
+
const botId=currentBotId();
|
|
1293
|
+
if (!activeProjectDir()) { state.featureFlags = {}; state.featureInstalled = {}; state.featureVersions = {}; if (!silent) render(); return; }
|
|
1294
|
+
try {
|
|
1295
|
+
const data = (await api('/api/features' + projectQuery({ agentId: botId }))) || {};
|
|
1296
|
+
state.featureFlags = data.flags || {};
|
|
1297
|
+
state.featureInstalled = data.installed || {};
|
|
1298
|
+
state.featureVersions = data.versions || {};
|
|
1299
|
+
} catch (_) {
|
|
1300
|
+
state.featureFlags = {};
|
|
1301
|
+
state.featureInstalled = {};
|
|
1302
|
+
state.featureVersions = {};
|
|
1303
|
+
}
|
|
1304
|
+
if (!silent) render();
|
|
1305
|
+
}
|
|
1305
1306
|
function appendLogLine(line) {
|
|
1306
1307
|
if (line.includes('Setup Wizard updated successfully! Please restart the installer.')) {
|
|
1307
1308
|
showToast(t('Đang khởi động lại UI', 'Restarting Setup UI'), t('Hệ thống đang tự khởi động lại để áp dụng phiên bản mới...', 'The system is restarting to apply the new version...'), 'info', 12000);
|
|
@@ -1404,10 +1405,10 @@ function showToast(title, desc, type = 'info', duration = 4000) {
|
|
|
1404
1405
|
}
|
|
1405
1406
|
}
|
|
1406
1407
|
|
|
1407
|
-
render();
|
|
1408
|
-
Promise.allSettled([loadSystem(true), loadStatus(true), loadCatalog(true)])
|
|
1409
|
-
.then(() => loadFeatureFlags(true).catch(() => {}))
|
|
1410
|
-
.finally(() => { render(); connectLogs(); });
|
|
1408
|
+
render();
|
|
1409
|
+
Promise.allSettled([loadSystem(true), loadStatus(true), loadCatalog(true)])
|
|
1410
|
+
.then(() => loadFeatureFlags(true).catch(() => {}))
|
|
1411
|
+
.finally(() => { render(); connectLogs(); });
|
|
1411
1412
|
|
|
1412
1413
|
|
|
1413
1414
|
|