daedalus-cli 3.74.0 → 3.76.0
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/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/dist/agents/roles.d.ts.map +1 -1
- package/dist/agents/roles.js +4 -1
- package/dist/agents/roles.js.map +1 -1
- package/dist/commands/webui.js +5 -5
- package/dist/commands/webui.js.map +1 -1
- package/dist/formatting.d.ts +1 -1
- package/dist/formatting.d.ts.map +1 -1
- package/dist/formatting.js +6 -2
- package/dist/formatting.js.map +1 -1
- package/dist/marathon/engine.js +1 -1
- package/dist/marathon/engine.js.map +1 -1
- package/dist/marathon/evaluator.d.ts +1 -0
- package/dist/marathon/evaluator.d.ts.map +1 -1
- package/dist/marathon/evaluator.js +33 -5
- package/dist/marathon/evaluator.js.map +1 -1
- package/dist/marathon/planner.d.ts.map +1 -1
- package/dist/marathon/planner.js +2 -1
- package/dist/marathon/planner.js.map +1 -1
- package/dist/repl.js +1 -1
- package/dist/repl.js.map +1 -1
- package/dist/tools/builtin/files.d.ts.map +1 -1
- package/dist/tools/builtin/files.js +11 -1
- package/dist/tools/builtin/files.js.map +1 -1
- package/dist/webui/install.test.d.ts +2 -0
- package/dist/webui/install.test.d.ts.map +1 -0
- package/dist/webui/install.test.js +43 -0
- package/dist/webui/install.test.js.map +1 -0
- package/dist/webui/public/apple-touch-icon.png +0 -0
- package/dist/webui/public/icon-192.png +0 -0
- package/dist/webui/public/icon-512.png +0 -0
- package/dist/webui/public/index.html +116 -32
- package/dist/webui/public/logo.png +0 -0
- package/dist/webui/public/manifest.json +25 -0
- package/dist/webui/public/script.js +404 -9
- package/dist/webui/public/styles.css +773 -3
- package/dist/webui/public/sw.js +81 -0
- package/dist/webui/public/types.d.ts +9 -0
- package/dist/webui/public/types.d.ts.map +1 -0
- package/dist/webui/public/types.js +2 -0
- package/dist/webui/public/types.js.map +1 -0
- package/dist/webui/public/types.ts +8 -0
- package/dist/webui/qr.d.ts +23 -0
- package/dist/webui/qr.d.ts.map +1 -0
- package/dist/webui/qr.js +60 -0
- package/dist/webui/qr.js.map +1 -0
- package/dist/webui/qr.test.d.ts +2 -0
- package/dist/webui/qr.test.d.ts.map +1 -0
- package/dist/webui/qr.test.js +34 -0
- package/dist/webui/qr.test.js.map +1 -0
- package/dist/webui/server.d.ts +3 -2
- package/dist/webui/server.d.ts.map +1 -1
- package/dist/webui/server.js +99 -35
- package/dist/webui/server.js.map +1 -1
- package/dist/webui/server.test.js +33 -1
- package/dist/webui/server.test.js.map +1 -1
- package/dist/webui/styles.test.d.ts +2 -0
- package/dist/webui/styles.test.d.ts.map +1 -0
- package/dist/webui/styles.test.js +19 -0
- package/dist/webui/styles.test.js.map +1 -0
- package/dist/webui/ws.d.ts +17 -0
- package/dist/webui/ws.d.ts.map +1 -0
- package/dist/webui/ws.js +57 -0
- package/dist/webui/ws.js.map +1 -0
- package/dist/webui/ws.test.d.ts +2 -0
- package/dist/webui/ws.test.d.ts.map +1 -0
- package/dist/webui/ws.test.js +66 -0
- package/dist/webui/ws.test.js.map +1 -0
- package/package.json +4 -1
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
// Service worker registration
|
|
2
|
+
if ('serviceWorker' in navigator) {
|
|
3
|
+
navigator.serviceWorker.register('/sw.js')
|
|
4
|
+
.then(registration => {
|
|
5
|
+
console.log('[script.js] Service worker registered successfully:', registration);
|
|
6
|
+
|
|
7
|
+
registration.addEventListener('updatefound', () => {
|
|
8
|
+
console.log('[script.js] Service worker update found');
|
|
9
|
+
const newWorker = registration.installing;
|
|
10
|
+
if (newWorker) {
|
|
11
|
+
newWorker.addEventListener('statechange', event => {
|
|
12
|
+
if (event.target.state === 'installed') {
|
|
13
|
+
if (navigator.serviceWorker.controller) {
|
|
14
|
+
console.log('[script.js] New service worker installed, page will reload on next navigation');
|
|
15
|
+
addLog('Service worker update available. Page will reload on next visit.');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
})
|
|
22
|
+
.catch(error => {
|
|
23
|
+
console.warn('[script.js] Service worker registration failed:', error);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
let refreshing = false;
|
|
27
|
+
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
28
|
+
if (!refreshing) {
|
|
29
|
+
refreshing = true;
|
|
30
|
+
console.log('[script.js] Service worker controller changed, reloading page');
|
|
31
|
+
window.location.reload();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
1
36
|
const statusEl = document.getElementById('connection-status');
|
|
2
37
|
const lastUpdateEl = document.getElementById('last-update');
|
|
3
38
|
const logContainer = document.getElementById('log-container');
|
|
@@ -173,6 +208,7 @@ function attachCliFooter(msgEl, modelName, toolCount, durationMs, tokenCount) {
|
|
|
173
208
|
msgEl.appendChild(footer);
|
|
174
209
|
}
|
|
175
210
|
|
|
211
|
+
let userProfileName = 'YOU';
|
|
176
212
|
let activeAssistantBody = null;
|
|
177
213
|
let activeAssistantMsgEl = null;
|
|
178
214
|
let thinkingEl = null;
|
|
@@ -191,7 +227,7 @@ function addChatMessage(role, text, roleBadge = null, imageBase64 = null, timest
|
|
|
191
227
|
|
|
192
228
|
const sender = document.createElement('span');
|
|
193
229
|
sender.className = 'sender';
|
|
194
|
-
sender.textContent = role === 'user' ?
|
|
230
|
+
sender.textContent = role === 'user' ? userProfileName : 'DAEDALUS';
|
|
195
231
|
header.appendChild(sender);
|
|
196
232
|
|
|
197
233
|
if (roleBadge) {
|
|
@@ -286,7 +322,7 @@ function renderToolStart(toolName) {
|
|
|
286
322
|
|
|
287
323
|
const icon = document.createElement('span');
|
|
288
324
|
icon.className = 'tool-tree-icon';
|
|
289
|
-
icon.
|
|
325
|
+
icon.innerHTML = '<svg style="width: 12px; height: 12px; display: inline-block; vertical-align: -1px;" viewBox="0 0 24 24" fill="currentColor"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>';
|
|
290
326
|
|
|
291
327
|
const label = document.createElement('span');
|
|
292
328
|
label.className = 'tool-tree-label';
|
|
@@ -322,16 +358,15 @@ function renderToolStart(toolName) {
|
|
|
322
358
|
details.className = 'tool-tree-details hidden';
|
|
323
359
|
|
|
324
360
|
header.addEventListener('click', () => {
|
|
325
|
-
|
|
326
|
-
|
|
361
|
+
const isHidden = details.classList.toggle('hidden');
|
|
362
|
+
chevron.style.transform = isHidden ? 'rotate(0deg)' : 'rotate(90deg)';
|
|
327
363
|
});
|
|
328
364
|
|
|
329
365
|
tree.appendChild(header);
|
|
330
366
|
tree.appendChild(details);
|
|
331
367
|
chatMessages.appendChild(tree);
|
|
332
|
-
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
333
|
-
|
|
334
368
|
currentToolTreeEl = tree;
|
|
369
|
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
335
370
|
}
|
|
336
371
|
|
|
337
372
|
const label = currentToolTreeEl.querySelector('.tool-tree-label');
|
|
@@ -364,7 +399,7 @@ function renderToolResult(toolName, resultSummary) {
|
|
|
364
399
|
|
|
365
400
|
const summary = document.createElement('span');
|
|
366
401
|
summary.className = 'tool-tree-item-summary';
|
|
367
|
-
summary.textContent = resultSummary ? `(${resultSummary})` : '
|
|
402
|
+
summary.textContent = resultSummary ? `(${resultSummary})` : 'completed';
|
|
368
403
|
|
|
369
404
|
item.appendChild(rail);
|
|
370
405
|
item.appendChild(name);
|
|
@@ -380,7 +415,7 @@ function renderToolResult(toolName, resultSummary) {
|
|
|
380
415
|
const badge = currentToolTreeEl.querySelector('.tool-tree-badge');
|
|
381
416
|
if (badge) {
|
|
382
417
|
badge.className = 'tool-tree-badge completed';
|
|
383
|
-
badge.textContent = '
|
|
418
|
+
badge.textContent = 'DONE';
|
|
384
419
|
}
|
|
385
420
|
|
|
386
421
|
currentToolTreeEl.classList.remove('running');
|
|
@@ -1216,12 +1251,372 @@ if (modelModal) {
|
|
|
1216
1251
|
});
|
|
1217
1252
|
}
|
|
1218
1253
|
|
|
1254
|
+
// ─────────────────────────────────────────────────────────────
|
|
1255
|
+
// QR Code Pairing Modal — M-5 / M-6
|
|
1256
|
+
// ─────────────────────────────────────────────────────────────
|
|
1257
|
+
const qrPairBtn = document.getElementById('qr-pair-btn');
|
|
1258
|
+
const qrModal = document.getElementById('qr-modal');
|
|
1259
|
+
const qrModalClose = document.getElementById('qr-modal-close');
|
|
1260
|
+
const qrImage = document.getElementById('qr-image');
|
|
1261
|
+
const qrWsUrl = document.getElementById('qr-ws-url');
|
|
1262
|
+
|
|
1263
|
+
async function openQrModal() {
|
|
1264
|
+
if (qrModal) {
|
|
1265
|
+
qrModal.classList.remove('hidden');
|
|
1266
|
+
let webUrl = window.location.origin;
|
|
1267
|
+
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
|
1268
|
+
try {
|
|
1269
|
+
const res = await fetch('/api/pairing-url');
|
|
1270
|
+
if (res.ok) {
|
|
1271
|
+
const data = await res.json();
|
|
1272
|
+
if (data.url) webUrl = data.url;
|
|
1273
|
+
}
|
|
1274
|
+
} catch {}
|
|
1275
|
+
}
|
|
1276
|
+
if (qrWsUrl) {
|
|
1277
|
+
qrWsUrl.textContent = webUrl;
|
|
1278
|
+
}
|
|
1279
|
+
if (qrImage) {
|
|
1280
|
+
qrImage.src = `/api/qr?url=${encodeURIComponent(webUrl)}&t=${Date.now()}`;
|
|
1281
|
+
}
|
|
1282
|
+
addLog(`QR pairing portal opened: <strong>${webUrl}</strong>`);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
function closeQrModal() {
|
|
1287
|
+
if (qrModal) {
|
|
1288
|
+
qrModal.classList.add('hidden');
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if (qrPairBtn) {
|
|
1293
|
+
qrPairBtn.addEventListener('click', openQrModal);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
if (qrModalClose) {
|
|
1297
|
+
qrModalClose.addEventListener('click', closeQrModal);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
if (qrModal) {
|
|
1301
|
+
qrModal.addEventListener('click', (e) => {
|
|
1302
|
+
if (e.target === qrModal) closeQrModal();
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
async function loadUserProfile() {
|
|
1307
|
+
try {
|
|
1308
|
+
const res = await fetch('/api/profile');
|
|
1309
|
+
if (!res.ok) return;
|
|
1310
|
+
const data = await res.json();
|
|
1311
|
+
if (data.name && data.name.trim()) {
|
|
1312
|
+
userProfileName = data.name.trim().toUpperCase();
|
|
1313
|
+
document.querySelectorAll('.chat-msg.user .sender').forEach(el => {
|
|
1314
|
+
el.textContent = userProfileName;
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
} catch {}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// ─────────────────────────────────────────────────────────────
|
|
1321
|
+
// Touch-Optimized UI — M-4: TouchTargetConfig interface
|
|
1322
|
+
// ─────────────────────────────────────────────────────────────
|
|
1323
|
+
/**
|
|
1324
|
+
* @typedef {Object} TouchTargetConfig
|
|
1325
|
+
* @property {string} selector - CSS selector for the interactive element
|
|
1326
|
+
* @property {number} [minSizePx=48] - Minimum width and height in pixels
|
|
1327
|
+
* @property {string} [touchAction='manipulation'] - touch-action CSS value
|
|
1328
|
+
*/
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* Apply touch optimizations by injecting CSS for minimum tap targets
|
|
1332
|
+
* and touch-action on elements matching the given selectors.
|
|
1333
|
+
* @param {TouchTargetConfig[]} configs
|
|
1334
|
+
*/
|
|
1335
|
+
function applyTouchOptimizations(configs) {
|
|
1336
|
+
if (!configs || configs.length === 0) return;
|
|
1337
|
+
|
|
1338
|
+
const styleId = 'touch-optimization-styles';
|
|
1339
|
+
let styleEl = document.getElementById(styleId);
|
|
1340
|
+
|
|
1341
|
+
if (!styleEl) {
|
|
1342
|
+
styleEl = document.createElement('style');
|
|
1343
|
+
styleEl.id = styleId;
|
|
1344
|
+
document.head.appendChild(styleEl);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
const rules = configs.map(function (cfg) {
|
|
1348
|
+
var minSize = cfg.minSizePx !== undefined ? cfg.minSizePx : 48;
|
|
1349
|
+
var touchAction = cfg.touchAction !== undefined ? cfg.touchAction : 'manipulation';
|
|
1350
|
+
var sel = cfg.selector;
|
|
1351
|
+
return [
|
|
1352
|
+
sel + ' {',
|
|
1353
|
+
' min-width: ' + minSize + 'px;',
|
|
1354
|
+
' min-height: ' + minSize + 'px;',
|
|
1355
|
+
' touch-action: ' + touchAction + ';',
|
|
1356
|
+
'}',
|
|
1357
|
+
].join('\n');
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
styleEl.textContent = rules.join('\n');
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// ─────────────────────────────────────────────────────────────
|
|
1364
|
+
// Critical element pointerdown listeners — M-4
|
|
1365
|
+
// ─────────────────────────────────────────────────────────────
|
|
1366
|
+
/**
|
|
1367
|
+
* Add both click and pointerdown listeners to ensure tactile responsiveness.
|
|
1368
|
+
* pointerdown fires before click and works for both touch and mouse.
|
|
1369
|
+
* @param {HTMLElement|null} el
|
|
1370
|
+
* @param {() => void} handler
|
|
1371
|
+
*/
|
|
1372
|
+
function addDualInteractionListeners(el, handler) {
|
|
1373
|
+
if (!el) return;
|
|
1374
|
+
el.addEventListener('pointerdown', function (e) {
|
|
1375
|
+
e.preventDefault();
|
|
1376
|
+
handler();
|
|
1377
|
+
});
|
|
1378
|
+
// Keep click for keyboard / accessibility
|
|
1379
|
+
if (!el.dataset._hasClickBound) {
|
|
1380
|
+
el.addEventListener('click', handler);
|
|
1381
|
+
el.dataset._hasClickBound = 'true';
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// Wire up pointerdown on all critical interactive elements
|
|
1386
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
1387
|
+
// #active-model-badge — opens model selection modal
|
|
1388
|
+
addDualInteractionListeners(
|
|
1389
|
+
document.getElementById('active-model-badge'),
|
|
1390
|
+
openModelModal
|
|
1391
|
+
);
|
|
1392
|
+
|
|
1393
|
+
// #clear-log — clears the log panel
|
|
1394
|
+
addDualInteractionListeners(
|
|
1395
|
+
document.getElementById('clear-log'),
|
|
1396
|
+
function () {
|
|
1397
|
+
if (logContainer) logContainer.innerHTML = '';
|
|
1398
|
+
}
|
|
1399
|
+
);
|
|
1400
|
+
|
|
1401
|
+
// #new-chat-btn — starts a new chat session
|
|
1402
|
+
addDualInteractionListeners(
|
|
1403
|
+
document.getElementById('new-chat-btn'),
|
|
1404
|
+
function () {
|
|
1405
|
+
if (chatMessages) chatMessages.innerHTML = '';
|
|
1406
|
+
addLog('New rite initiated. The cosmos awaits your query.');
|
|
1407
|
+
}
|
|
1408
|
+
);
|
|
1409
|
+
|
|
1410
|
+
// #attach-btn — triggers file attachment
|
|
1411
|
+
addDualInteractionListeners(
|
|
1412
|
+
document.getElementById('attach-btn'),
|
|
1413
|
+
function () {
|
|
1414
|
+
var fileInput = document.getElementById('file-input');
|
|
1415
|
+
if (fileInput) fileInput.click();
|
|
1416
|
+
}
|
|
1417
|
+
);
|
|
1418
|
+
|
|
1419
|
+
// #send-btn — submits the chat form
|
|
1420
|
+
addDualInteractionListeners(
|
|
1421
|
+
document.getElementById('send-btn'),
|
|
1422
|
+
function () {
|
|
1423
|
+
var form = document.getElementById('chat-form');
|
|
1424
|
+
if (form) form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
|
1425
|
+
}
|
|
1426
|
+
);
|
|
1427
|
+
|
|
1428
|
+
// #model-modal-close — closes model selection modal
|
|
1429
|
+
addDualInteractionListeners(
|
|
1430
|
+
document.getElementById('model-modal-close'),
|
|
1431
|
+
closeModelModal
|
|
1432
|
+
);
|
|
1433
|
+
|
|
1434
|
+
// #qr-pair-btn — opens QR pairing modal
|
|
1435
|
+
addDualInteractionListeners(
|
|
1436
|
+
document.getElementById('qr-pair-btn'),
|
|
1437
|
+
openQrModal
|
|
1438
|
+
);
|
|
1439
|
+
|
|
1440
|
+
// #qr-modal-close — closes QR pairing modal
|
|
1441
|
+
addDualInteractionListeners(
|
|
1442
|
+
document.getElementById('qr-modal-close'),
|
|
1443
|
+
closeQrModal
|
|
1444
|
+
);
|
|
1445
|
+
|
|
1446
|
+
// Apply global touch optimizations via CSS
|
|
1447
|
+
applyTouchOptimizations([
|
|
1448
|
+
{ selector: 'button', minSizePx: 48, touchAction: 'manipulation' },
|
|
1449
|
+
{ selector: 'a[href]', minSizePx: 48, touchAction: 'manipulation' },
|
|
1450
|
+
{ selector: '.model-pill', minSizePx: 44, touchAction: 'manipulation' },
|
|
1451
|
+
{ selector: '.clear-btn', minSizePx: 48, touchAction: 'manipulation' },
|
|
1452
|
+
{ selector: '.chat-header-btn', minSizePx: 48, touchAction: 'manipulation' },
|
|
1453
|
+
{ selector: '.attach-btn', minSizePx: 44, touchAction: 'manipulation' },
|
|
1454
|
+
{ selector: '#send-btn', minSizePx: 48, touchAction: 'manipulation' },
|
|
1455
|
+
{ selector: '.model-option-card', minSizePx: 48, touchAction: 'manipulation' },
|
|
1456
|
+
{ selector: '.code-copy-btn', minSizePx: 36, touchAction: 'manipulation' },
|
|
1457
|
+
]);
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
// ─────────────────────────────────────────────────────────────
|
|
1461
|
+
// WebSocket & Milestone Push Notifications — M-7
|
|
1462
|
+
// ─────────────────────────────────────────────────────────────
|
|
1463
|
+
let wsClient = null;
|
|
1464
|
+
let wsReconnectTimer = null;
|
|
1465
|
+
|
|
1466
|
+
function showMilestoneNotification(payload) {
|
|
1467
|
+
if (!payload || payload.type !== 'milestone') return;
|
|
1468
|
+
|
|
1469
|
+
const title = `[DAEDALUS] Milestone ${payload.id ? payload.id.toUpperCase() : ''}: ${payload.title || 'Update'}`;
|
|
1470
|
+
const options = {
|
|
1471
|
+
body: payload.summary || `Status: ${(payload.status || 'passed').toUpperCase()}${payload.score !== undefined ? ` (${payload.score}/100)` : ''}`,
|
|
1472
|
+
icon: '/favicon.svg',
|
|
1473
|
+
badge: '/favicon.svg',
|
|
1474
|
+
tag: `daedalus-milestone-${payload.id || Date.now()}`,
|
|
1475
|
+
renotify: true,
|
|
1476
|
+
};
|
|
1477
|
+
|
|
1478
|
+
if ('Notification' in window) {
|
|
1479
|
+
if (Notification.permission === 'granted') {
|
|
1480
|
+
try {
|
|
1481
|
+
new Notification(title, options);
|
|
1482
|
+
} catch {}
|
|
1483
|
+
} else if (Notification.permission !== 'denied') {
|
|
1484
|
+
Notification.requestPermission().then(function (permission) {
|
|
1485
|
+
if (permission === 'granted') {
|
|
1486
|
+
try {
|
|
1487
|
+
new Notification(title, options);
|
|
1488
|
+
} catch {}
|
|
1489
|
+
}
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// Also log into Oracle system log
|
|
1495
|
+
addLog(`Milestone <strong>${payload.id || ''}</strong>: ${payload.title} — <em>${(payload.status || 'passed').toUpperCase()}</em>`);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
function connectWebSocket() {
|
|
1499
|
+
if (wsClient && (wsClient.readyState === WebSocket.OPEN || wsClient.readyState === WebSocket.CONNECTING)) {
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1504
|
+
const wsUrl = `${protocol}//${window.location.host}`;
|
|
1505
|
+
|
|
1506
|
+
try {
|
|
1507
|
+
wsClient = new WebSocket(wsUrl);
|
|
1508
|
+
|
|
1509
|
+
wsClient.onopen = function () {
|
|
1510
|
+
if (wsReconnectTimer) {
|
|
1511
|
+
clearTimeout(wsReconnectTimer);
|
|
1512
|
+
wsReconnectTimer = null;
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
|
|
1516
|
+
wsClient.onmessage = function (event) {
|
|
1517
|
+
try {
|
|
1518
|
+
const data = JSON.parse(event.data);
|
|
1519
|
+
if (data.type === 'milestone') {
|
|
1520
|
+
showMilestoneNotification(data);
|
|
1521
|
+
}
|
|
1522
|
+
} catch {}
|
|
1523
|
+
};
|
|
1524
|
+
|
|
1525
|
+
wsClient.onclose = function () {
|
|
1526
|
+
wsClient = null;
|
|
1527
|
+
if (!wsReconnectTimer) {
|
|
1528
|
+
wsReconnectTimer = setTimeout(connectWebSocket, 3000);
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1531
|
+
|
|
1532
|
+
wsClient.onerror = function () {
|
|
1533
|
+
try { wsClient.close(); } catch {}
|
|
1534
|
+
};
|
|
1535
|
+
} catch {}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// ─────────────────────────────────────────────────────────────
|
|
1539
|
+
// Bootstrapping
|
|
1540
|
+
// ─────────────────────────────────────────────────────────────
|
|
1219
1541
|
connectSSE();
|
|
1542
|
+
connectWebSocket();
|
|
1220
1543
|
loadModels();
|
|
1544
|
+
loadUserProfile();
|
|
1221
1545
|
|
|
1222
|
-
setInterval(()
|
|
1546
|
+
setInterval(function () {
|
|
1223
1547
|
if (lastUpdateEl) {
|
|
1224
1548
|
lastUpdateEl.textContent = new Date().toLocaleTimeString();
|
|
1225
1549
|
}
|
|
1226
1550
|
}, 1000);
|
|
1227
1551
|
|
|
1552
|
+
// ─────────────────────────────────────────────────────────────
|
|
1553
|
+
// PWA Install Prompt Handling — M-8
|
|
1554
|
+
// ─────────────────────────────────────────────────────────────
|
|
1555
|
+
let deferredPrompt = null;
|
|
1556
|
+
const installBanner = document.getElementById('install-banner');
|
|
1557
|
+
const installButton = document.getElementById('install-button');
|
|
1558
|
+
const installBannerCloseButtons = document.querySelectorAll('.install-banner-close, .install-banner-dismiss-btn');
|
|
1559
|
+
|
|
1560
|
+
function showInstallBanner() {
|
|
1561
|
+
if (installBanner) {
|
|
1562
|
+
installBanner.classList.remove('hidden');
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
function hideInstallBanner() {
|
|
1567
|
+
if (installBanner) {
|
|
1568
|
+
installBanner.classList.add('hidden');
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
window.addEventListener('beforeinstallprompt', function (event) {
|
|
1573
|
+
event.preventDefault();
|
|
1574
|
+
deferredPrompt = event;
|
|
1575
|
+
console.log('[script.js] beforeinstallprompt captured, install banner ready');
|
|
1576
|
+
showInstallBanner();
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
if (installButton) {
|
|
1580
|
+
installButton.addEventListener('click', async function () {
|
|
1581
|
+
if (!deferredPrompt) {
|
|
1582
|
+
console.warn('[script.js] No deferred install prompt available');
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
try {
|
|
1587
|
+
const result = await deferredPrompt.prompt();
|
|
1588
|
+
const choice = result && result.outcome ? result.outcome : 'unknown';
|
|
1589
|
+
|
|
1590
|
+
if (choice === 'accepted') {
|
|
1591
|
+
console.log('[script.js] User accepted PWA install prompt');
|
|
1592
|
+
addLog('PWA install accepted. Daedalus will be added to your home screen.');
|
|
1593
|
+
} else {
|
|
1594
|
+
console.log('[script.js] User dismissed PWA install prompt:', choice);
|
|
1595
|
+
addLog('PWA install dismissed by user.');
|
|
1596
|
+
}
|
|
1597
|
+
} catch (err) {
|
|
1598
|
+
console.error('[script.js] PWA install prompt failed:', err);
|
|
1599
|
+
addLog('PWA install prompt encountered an error.');
|
|
1600
|
+
} finally {
|
|
1601
|
+
deferredPrompt = null;
|
|
1602
|
+
hideInstallBanner();
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
installBannerCloseButtons.forEach(btn => {
|
|
1608
|
+
btn.addEventListener('click', function () {
|
|
1609
|
+
console.log('[script.js] Install banner dismissed by user');
|
|
1610
|
+
deferredPrompt = null;
|
|
1611
|
+
hideInstallBanner();
|
|
1612
|
+
});
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1615
|
+
window.addEventListener('appinstalled', function () {
|
|
1616
|
+
console.log('[script.js] PWA installed successfully');
|
|
1617
|
+
addLog('Daedalus has been installed as an app.');
|
|
1618
|
+
deferredPrompt = null;
|
|
1619
|
+
hideInstallBanner();
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1622
|
+
|