pinokiod 3.180.0 → 3.182.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/kernel/favicon.js +91 -34
- package/kernel/peer.js +73 -0
- package/kernel/util.js +28 -4
- package/package.json +1 -1
- package/server/index.js +237 -35
- package/server/public/common.js +677 -240
- package/server/public/files-app/app.css +64 -0
- package/server/public/files-app/app.js +87 -0
- package/server/public/install.js +8 -1
- package/server/public/layout.js +124 -0
- package/server/public/nav.js +227 -64
- package/server/public/sound/beep.mp3 +0 -0
- package/server/public/sound/bell.mp3 +0 -0
- package/server/public/sound/bright-ring.mp3 +0 -0
- package/server/public/sound/clap.mp3 +0 -0
- package/server/public/sound/deep-ring.mp3 +0 -0
- package/server/public/sound/gasp.mp3 +0 -0
- package/server/public/sound/hehe.mp3 +0 -0
- package/server/public/sound/levelup.mp3 +0 -0
- package/server/public/sound/light-pop.mp3 +0 -0
- package/server/public/sound/light-ring.mp3 +0 -0
- package/server/public/sound/meow.mp3 +0 -0
- package/server/public/sound/piano.mp3 +0 -0
- package/server/public/sound/pop.mp3 +0 -0
- package/server/public/sound/uhoh.mp3 +0 -0
- package/server/public/sound/whistle.mp3 +0 -0
- package/server/public/style.css +195 -4
- package/server/public/tab-idle-notifier.js +700 -4
- package/server/public/terminal-settings.js +1131 -0
- package/server/public/urldropdown.css +28 -1
- package/server/socket.js +71 -4
- package/server/views/{terminals.ejs → agents.ejs} +108 -32
- package/server/views/app.ejs +321 -104
- package/server/views/bootstrap.ejs +8 -0
- package/server/views/connect.ejs +10 -1
- package/server/views/d.ejs +172 -18
- package/server/views/editor.ejs +8 -0
- package/server/views/file_browser.ejs +4 -0
- package/server/views/index.ejs +10 -1
- package/server/views/init/index.ejs +18 -3
- package/server/views/install.ejs +8 -0
- package/server/views/layout.ejs +2 -0
- package/server/views/net.ejs +10 -1
- package/server/views/network.ejs +10 -1
- package/server/views/pro.ejs +8 -0
- package/server/views/prototype/index.ejs +8 -0
- package/server/views/screenshots.ejs +10 -2
- package/server/views/settings.ejs +10 -2
- package/server/views/shell.ejs +8 -0
- package/server/views/terminal.ejs +8 -0
- package/server/views/tools.ejs +10 -2
package/server/public/common.js
CHANGED
|
@@ -1462,10 +1462,56 @@ if (typeof hotkeys === 'function') {
|
|
|
1462
1462
|
})
|
|
1463
1463
|
}
|
|
1464
1464
|
|
|
1465
|
+
// Stable per-browser device identifier
|
|
1466
|
+
(function initPinokioDeviceId() {
|
|
1467
|
+
if (typeof window === 'undefined') {
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
try {
|
|
1471
|
+
const KEY = 'pinokio:device-id';
|
|
1472
|
+
const gen = () => `${Date.now()}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;
|
|
1473
|
+
const get = () => {
|
|
1474
|
+
try {
|
|
1475
|
+
let id = localStorage.getItem(KEY);
|
|
1476
|
+
if (typeof id !== 'string' || id.length < 8) {
|
|
1477
|
+
id = gen();
|
|
1478
|
+
localStorage.setItem(KEY, id);
|
|
1479
|
+
}
|
|
1480
|
+
return id;
|
|
1481
|
+
} catch (_) {
|
|
1482
|
+
// Fallback when localStorage is unavailable
|
|
1483
|
+
if (!window.__pinokioVolatileDeviceId) {
|
|
1484
|
+
window.__pinokioVolatileDeviceId = gen();
|
|
1485
|
+
}
|
|
1486
|
+
return window.__pinokioVolatileDeviceId;
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
// Expose helpers
|
|
1490
|
+
if (!window.PinokioGetDeviceId) {
|
|
1491
|
+
window.PinokioGetDeviceId = get;
|
|
1492
|
+
}
|
|
1493
|
+
// Convenience alias
|
|
1494
|
+
window.PinokioDeviceId = get();
|
|
1495
|
+
} catch (_) {
|
|
1496
|
+
// ignore
|
|
1497
|
+
}
|
|
1498
|
+
})();
|
|
1499
|
+
|
|
1465
1500
|
(function initNotificationAudioBridge() {
|
|
1466
1501
|
if (typeof window === 'undefined') {
|
|
1467
1502
|
return;
|
|
1468
1503
|
}
|
|
1504
|
+
// Avoid duplicate audio playback: if this is the top-level layout page, or if the
|
|
1505
|
+
// top window already owns notification playback, skip initialising this bridge.
|
|
1506
|
+
try {
|
|
1507
|
+
const isTop = window.top === window;
|
|
1508
|
+
if (isTop && document.getElementById('layout-root')) {
|
|
1509
|
+
return; // layout shell handles notifications
|
|
1510
|
+
}
|
|
1511
|
+
if (!isTop && window.top && window.top.__pinokioTopNotifyListener) {
|
|
1512
|
+
return; // top-level listener active; avoid duplicates from iframes
|
|
1513
|
+
}
|
|
1514
|
+
} catch (_) {}
|
|
1469
1515
|
if (window.__pinokioNotificationAudioInitialized) {
|
|
1470
1516
|
return;
|
|
1471
1517
|
}
|
|
@@ -1478,6 +1524,64 @@ if (typeof hotkeys === 'function') {
|
|
|
1478
1524
|
let reconnectTimeout = null;
|
|
1479
1525
|
let activeAudio = null;
|
|
1480
1526
|
|
|
1527
|
+
// Lightweight visual indicator to confirm notification receipt (mobile-friendly)
|
|
1528
|
+
let notifyIndicatorEl = null;
|
|
1529
|
+
let notifyIndicatorStyleInjected = false;
|
|
1530
|
+
const ensureNotifyIndicator = () => {
|
|
1531
|
+
if (!notifyIndicatorStyleInjected) {
|
|
1532
|
+
try {
|
|
1533
|
+
const style = document.createElement('style');
|
|
1534
|
+
style.textContent = `
|
|
1535
|
+
.pinokio-notify-indicator{position:fixed;top:12px;right:12px;z-index:2147483647;display:none;align-items:center;gap:8px;padding:8px 10px;border-radius:999px;background:rgba(15,23,42,0.92);color:#fff;font:600 12px/1.2 system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;box-shadow:0 10px 30px rgba(0,0,0,0.35)}
|
|
1536
|
+
.pinokio-notify-indicator .bell{font-size:14px}
|
|
1537
|
+
.pinokio-notify-indicator.show{display:inline-flex;animation:pinokioNotifyPop 160ms ease-out, pinokioNotifyFade 1600ms ease-in 700ms forwards}
|
|
1538
|
+
@keyframes pinokioNotifyPop{from{transform:translateY(-6px) scale(.98);opacity:0}to{transform:translateY(0) scale(1);opacity:1}}
|
|
1539
|
+
@keyframes pinokioNotifyFade{to{opacity:0;transform:translateY(-4px)}}
|
|
1540
|
+
@media (max-width: 768px){.pinokio-notify-indicator{top:10px;right:10px;padding:7px 9px;font-size:12px}}
|
|
1541
|
+
`;
|
|
1542
|
+
document.head.appendChild(style);
|
|
1543
|
+
notifyIndicatorStyleInjected = true;
|
|
1544
|
+
} catch (_) {}
|
|
1545
|
+
}
|
|
1546
|
+
if (!notifyIndicatorEl) {
|
|
1547
|
+
try {
|
|
1548
|
+
const el = document.createElement('div');
|
|
1549
|
+
el.className = 'pinokio-notify-indicator';
|
|
1550
|
+
const icon = document.createElement('span');
|
|
1551
|
+
icon.className = 'bell';
|
|
1552
|
+
icon.textContent = '🔔';
|
|
1553
|
+
const text = document.createElement('span');
|
|
1554
|
+
text.className = 'text';
|
|
1555
|
+
text.textContent = 'Notification received';
|
|
1556
|
+
el.appendChild(icon);
|
|
1557
|
+
el.appendChild(text);
|
|
1558
|
+
document.body.appendChild(el);
|
|
1559
|
+
notifyIndicatorEl = el;
|
|
1560
|
+
} catch (_) {}
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
const flashNotifyIndicator = (payload) => {
|
|
1564
|
+
try {
|
|
1565
|
+
ensureNotifyIndicator();
|
|
1566
|
+
if (!notifyIndicatorEl) return;
|
|
1567
|
+
const text = notifyIndicatorEl.querySelector('.text');
|
|
1568
|
+
if (text) {
|
|
1569
|
+
const msg = (payload && typeof payload.message === 'string' && payload.message.trim()) ? payload.message.trim() : 'Notification received';
|
|
1570
|
+
// Keep it short on mobile
|
|
1571
|
+
text.textContent = msg.length > 80 ? (msg.slice(0, 77) + '…') : msg;
|
|
1572
|
+
}
|
|
1573
|
+
// retrigger animation
|
|
1574
|
+
notifyIndicatorEl.classList.remove('show');
|
|
1575
|
+
// force reflow
|
|
1576
|
+
void notifyIndicatorEl.offsetWidth;
|
|
1577
|
+
notifyIndicatorEl.classList.add('show');
|
|
1578
|
+
// Auto-hide handled by CSS animation; keep element for reuse
|
|
1579
|
+
window.setTimeout(() => {
|
|
1580
|
+
if (notifyIndicatorEl) notifyIndicatorEl.classList.remove('show');
|
|
1581
|
+
}, 2600);
|
|
1582
|
+
} catch (_) {}
|
|
1583
|
+
};
|
|
1584
|
+
|
|
1481
1585
|
const leaderStorageKey = 'pinokio.notification.leader';
|
|
1482
1586
|
const leaderHeartbeatMs = 5000;
|
|
1483
1587
|
const leaderStaleMs = 15000;
|
|
@@ -1616,8 +1720,10 @@ if (typeof hotkeys === 'function') {
|
|
|
1616
1720
|
}
|
|
1617
1721
|
};
|
|
1618
1722
|
|
|
1723
|
+
const isFalseyString = (value) => typeof value === 'string' && ['false', '0', 'no', 'off'].includes(value.trim().toLowerCase());
|
|
1724
|
+
|
|
1619
1725
|
const enqueueSound = (url) => {
|
|
1620
|
-
if (!url) {
|
|
1726
|
+
if (!url || url === false || isFalseyString(url)) {
|
|
1621
1727
|
return;
|
|
1622
1728
|
}
|
|
1623
1729
|
pendingSounds.push(url);
|
|
@@ -1629,6 +1735,18 @@ if (typeof hotkeys === 'function') {
|
|
|
1629
1735
|
return;
|
|
1630
1736
|
}
|
|
1631
1737
|
const payload = packet.data || {};
|
|
1738
|
+
// If targeted to a specific device, ignore only when our id exists and mismatches
|
|
1739
|
+
try {
|
|
1740
|
+
const targetId = (typeof payload.device_id === 'string' && payload.device_id.trim()) ? payload.device_id.trim() : null;
|
|
1741
|
+
if (targetId) {
|
|
1742
|
+
const myId = (typeof window.PinokioGetDeviceId === 'function') ? window.PinokioGetDeviceId() : null;
|
|
1743
|
+
if (myId && myId !== targetId) {
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
} catch (_) {}
|
|
1748
|
+
// Visual confirmation regardless of audio outcome (useful on mobile)
|
|
1749
|
+
flashNotifyIndicator(payload);
|
|
1632
1750
|
if (typeof payload.sound === 'string' && payload.sound) {
|
|
1633
1751
|
enqueueSound(payload.sound);
|
|
1634
1752
|
}
|
|
@@ -1696,6 +1814,7 @@ if (typeof hotkeys === 'function') {
|
|
|
1696
1814
|
{
|
|
1697
1815
|
method: CHANNEL_ID,
|
|
1698
1816
|
mode: 'listen',
|
|
1817
|
+
device_id: (typeof window.PinokioGetDeviceId === 'function') ? window.PinokioGetDeviceId() : undefined,
|
|
1699
1818
|
},
|
|
1700
1819
|
handlePacket
|
|
1701
1820
|
);
|
|
@@ -1768,6 +1887,127 @@ if (typeof hotkeys === 'function') {
|
|
|
1768
1887
|
// Attempt to become leader immediately on load.
|
|
1769
1888
|
attemptLeadership();
|
|
1770
1889
|
})();
|
|
1890
|
+
|
|
1891
|
+
// Mobile "Tap to connect" curtain to prime audio on the top-level page
|
|
1892
|
+
(function initMobileConnectCurtain() {
|
|
1893
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
try {
|
|
1897
|
+
if (window.__pinokioConnectCurtainInstalled || window.__pinokioConnectCurtainInstalling) {
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
} catch (_) {}
|
|
1901
|
+
try {
|
|
1902
|
+
if (window.top && window.top !== window) {
|
|
1903
|
+
return; // only top-level
|
|
1904
|
+
}
|
|
1905
|
+
} catch (_) {
|
|
1906
|
+
// cross-origin parent; just bail
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
if (window.__pinokioConnectCurtainInstalled) {
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
const isLikelyMobile = () => {
|
|
1914
|
+
try {
|
|
1915
|
+
if (navigator.userAgentData && typeof navigator.userAgentData.mobile === 'boolean') {
|
|
1916
|
+
if (navigator.userAgentData.mobile) return true;
|
|
1917
|
+
}
|
|
1918
|
+
} catch (_) {}
|
|
1919
|
+
try {
|
|
1920
|
+
const ua = (navigator.userAgent || '').toLowerCase();
|
|
1921
|
+
if (/iphone|ipad|ipod|android|mobile/.test(ua)) return true;
|
|
1922
|
+
} catch (_) {}
|
|
1923
|
+
try {
|
|
1924
|
+
if (navigator.maxTouchPoints && navigator.maxTouchPoints > 1) return true;
|
|
1925
|
+
} catch (_) {}
|
|
1926
|
+
try {
|
|
1927
|
+
if (window.matchMedia && window.matchMedia('(pointer: coarse)').matches) return true;
|
|
1928
|
+
} catch (_) {}
|
|
1929
|
+
try {
|
|
1930
|
+
if (window.matchMedia && window.matchMedia('(max-width: 900px)').matches) return true;
|
|
1931
|
+
} catch (_) {}
|
|
1932
|
+
return false;
|
|
1933
|
+
};
|
|
1934
|
+
|
|
1935
|
+
const createCurtain = () => {
|
|
1936
|
+
const style = document.createElement('style');
|
|
1937
|
+
style.textContent = `
|
|
1938
|
+
.pinokio-connect-curtain{position:fixed;top:0;left:0;right:0;bottom:0;z-index:2147483646;background:rgba(15,23,42,0.35);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center}
|
|
1939
|
+
.pinokio-connect-msg{user-select:none;-webkit-user-select:none;color:#fff;background:rgba(15,23,42,0.85);padding:14px 18px;border-radius:12px;font:600 16px/1.2 system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;box-shadow:0 16px 40px rgba(0,0,0,.38)}
|
|
1940
|
+
@media (max-width:768px){.pinokio-connect-msg{font-size:15px;padding:12px 16px}}
|
|
1941
|
+
`;
|
|
1942
|
+
document.head.appendChild(style);
|
|
1943
|
+
|
|
1944
|
+
const overlay = document.createElement('div');
|
|
1945
|
+
overlay.className = 'pinokio-connect-curtain';
|
|
1946
|
+
overlay.setAttribute('role', 'button');
|
|
1947
|
+
overlay.setAttribute('aria-label', 'Tap to connect');
|
|
1948
|
+
overlay.tabIndex = 0;
|
|
1949
|
+
const msg = document.createElement('div');
|
|
1950
|
+
msg.className = 'pinokio-connect-msg';
|
|
1951
|
+
msg.textContent = 'Tap to connect';
|
|
1952
|
+
overlay.appendChild(msg);
|
|
1953
|
+
window.__pinokioConnectCurtainInstalled = true;
|
|
1954
|
+
return overlay;
|
|
1955
|
+
};
|
|
1956
|
+
|
|
1957
|
+
const primeAudio = async () => {
|
|
1958
|
+
try {
|
|
1959
|
+
let a = window.__pinokioChimeAudio;
|
|
1960
|
+
if (!a) {
|
|
1961
|
+
a = new Audio('/chime.mp3');
|
|
1962
|
+
a.preload = 'auto';
|
|
1963
|
+
a.loop = false;
|
|
1964
|
+
a.muted = false;
|
|
1965
|
+
window.__pinokioChimeAudio = a;
|
|
1966
|
+
}
|
|
1967
|
+
a.currentTime = 0;
|
|
1968
|
+
await a.play(); // must be called synchronously in gesture handler
|
|
1969
|
+
try { a.pause(); a.currentTime = 0; } catch (_) {}
|
|
1970
|
+
try { window.__pinokioAudioArmed = true; } catch (_) {}
|
|
1971
|
+
return true;
|
|
1972
|
+
} catch (_) {
|
|
1973
|
+
try { window.__pinokioAudioArmed = true; } catch (_) {}
|
|
1974
|
+
return false;
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
|
|
1978
|
+
const setup = () => {
|
|
1979
|
+
let forceParam = false;
|
|
1980
|
+
try {
|
|
1981
|
+
const usp = new URLSearchParams(window.location.search);
|
|
1982
|
+
forceParam = usp.has('connect') || usp.get('connect') === '1';
|
|
1983
|
+
} catch (_) {}
|
|
1984
|
+
if (!(forceParam || isLikelyMobile())) {
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1987
|
+
if (window.__pinokioConnectCurtainInstalled || window.__pinokioConnectCurtainInstalling) {
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
try { window.__pinokioConnectCurtainInstalling = true; } catch (_) {}
|
|
1991
|
+
const overlay = createCurtain();
|
|
1992
|
+
let handled = false;
|
|
1993
|
+
const onTap = async (e) => {
|
|
1994
|
+
if (handled) return;
|
|
1995
|
+
handled = true;
|
|
1996
|
+
try { e.preventDefault(); e.stopPropagation(); } catch (_) {}
|
|
1997
|
+
try { await primeAudio(); } catch (_) {}
|
|
1998
|
+
try { overlay.remove(); } catch (_) {}
|
|
1999
|
+
try { window.__pinokioConnectCurtainInstalled = true; window.__pinokioConnectCurtainInstalling = false; } catch (_) {}
|
|
2000
|
+
};
|
|
2001
|
+
overlay.addEventListener('pointerdown', onTap, { once: true, capture: true });
|
|
2002
|
+
document.body.appendChild(overlay);
|
|
2003
|
+
};
|
|
2004
|
+
|
|
2005
|
+
if (document.readyState === 'loading') {
|
|
2006
|
+
document.addEventListener('DOMContentLoaded', setup, { once: true });
|
|
2007
|
+
} else {
|
|
2008
|
+
setup();
|
|
2009
|
+
}
|
|
2010
|
+
})();
|
|
1771
2011
|
const refreshParent = (e) => {
|
|
1772
2012
|
let dispatched = false;
|
|
1773
2013
|
if (typeof window !== 'undefined' && typeof window.PinokioBroadcastMessage === 'function') {
|
|
@@ -1967,6 +2207,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
1967
2207
|
}
|
|
1968
2208
|
if (document.querySelector("#refresh-page")) {
|
|
1969
2209
|
document.querySelector("#refresh-page").addEventListener("click", (e) => {
|
|
2210
|
+
try {
|
|
2211
|
+
const headerEl = document.querySelector("header.navheader");
|
|
2212
|
+
const isMinimized = !!(headerEl && headerEl.classList.contains("minimized"));
|
|
2213
|
+
const key = `pinokio:header-restore-once:${location.pathname}`;
|
|
2214
|
+
sessionStorage.setItem(key, isMinimized ? "1" : "0");
|
|
2215
|
+
} catch (_) {}
|
|
1970
2216
|
location.reload()
|
|
1971
2217
|
/*
|
|
1972
2218
|
let browserview = document.querySelector(".browserview")
|
|
@@ -2289,6 +2535,120 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
2289
2535
|
|
|
2290
2536
|
let createLauncherModalInstance = null;
|
|
2291
2537
|
let createLauncherKeydownHandler = null;
|
|
2538
|
+
let createLauncherModalPromise = null;
|
|
2539
|
+
|
|
2540
|
+
const createLauncherFallbackTools = [
|
|
2541
|
+
{
|
|
2542
|
+
value: 'claude',
|
|
2543
|
+
label: 'Claude Code',
|
|
2544
|
+
iconSrc: '/asset/plugin/code/claude/claude.png',
|
|
2545
|
+
isDefault: true,
|
|
2546
|
+
href: '/run/plugin/code/claude/pinokio.js',
|
|
2547
|
+
category: 'CLI',
|
|
2548
|
+
},
|
|
2549
|
+
{
|
|
2550
|
+
value: 'codex',
|
|
2551
|
+
label: 'OpenAI Codex',
|
|
2552
|
+
iconSrc: '/asset/plugin/code/codex/openai.webp',
|
|
2553
|
+
isDefault: false,
|
|
2554
|
+
href: '/run/plugin/code/codex/pinokio.js',
|
|
2555
|
+
category: 'CLI',
|
|
2556
|
+
},
|
|
2557
|
+
{
|
|
2558
|
+
value: 'gemini',
|
|
2559
|
+
label: 'Google Gemini CLI',
|
|
2560
|
+
iconSrc: '/asset/plugin/code/gemini/gemini.jpeg',
|
|
2561
|
+
isDefault: false,
|
|
2562
|
+
href: '/run/plugin/code/gemini/pinokio.js',
|
|
2563
|
+
category: 'CLI',
|
|
2564
|
+
},
|
|
2565
|
+
];
|
|
2566
|
+
|
|
2567
|
+
let cachedCreateLauncherTools = null;
|
|
2568
|
+
let loadingCreateLauncherTools = null;
|
|
2569
|
+
|
|
2570
|
+
function mapPluginMenuToCreateLauncherTools(menu) {
|
|
2571
|
+
if (!Array.isArray(menu)) return [];
|
|
2572
|
+
|
|
2573
|
+
return menu
|
|
2574
|
+
.map((plugin) => {
|
|
2575
|
+
if (!plugin || (!plugin.href && !plugin.link)) {
|
|
2576
|
+
return null;
|
|
2577
|
+
}
|
|
2578
|
+
const href = typeof plugin.href === 'string' ? plugin.href.trim() : '';
|
|
2579
|
+
const label = plugin.title || plugin.text || plugin.name || href || '';
|
|
2580
|
+
|
|
2581
|
+
let slug = '';
|
|
2582
|
+
if (href) {
|
|
2583
|
+
const segments = href.split('/').filter(Boolean);
|
|
2584
|
+
if (segments.length >= 2) {
|
|
2585
|
+
slug = segments[segments.length - 2] || '';
|
|
2586
|
+
}
|
|
2587
|
+
if (!slug && segments.length) {
|
|
2588
|
+
slug = segments[segments.length - 1] || '';
|
|
2589
|
+
}
|
|
2590
|
+
if (slug.endsWith('.js')) {
|
|
2591
|
+
slug = slug.replace(/\.js$/i, '');
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
if (!slug && label) {
|
|
2595
|
+
slug = label
|
|
2596
|
+
.toLowerCase()
|
|
2597
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
2598
|
+
.replace(/^-+|-+$/g, '');
|
|
2599
|
+
}
|
|
2600
|
+
const value = slug || href || (typeof plugin.link === 'string' ? plugin.link.trim() : '');
|
|
2601
|
+
if (!value) {
|
|
2602
|
+
return null;
|
|
2603
|
+
}
|
|
2604
|
+
const iconSrc = plugin.image || null;
|
|
2605
|
+
const runs = Array.isArray(plugin.run) ? plugin.run : [];
|
|
2606
|
+
const hasExec = runs.some((step) => step && step.method === 'exec');
|
|
2607
|
+
const category = hasExec ? 'IDE' : 'CLI';
|
|
2608
|
+
return {
|
|
2609
|
+
value,
|
|
2610
|
+
label,
|
|
2611
|
+
iconSrc,
|
|
2612
|
+
isDefault: Boolean(plugin.default === true),
|
|
2613
|
+
href: href || null,
|
|
2614
|
+
category,
|
|
2615
|
+
};
|
|
2616
|
+
})
|
|
2617
|
+
.filter(Boolean);
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
async function getCreateLauncherTools() {
|
|
2621
|
+
if (Array.isArray(cachedCreateLauncherTools) && cachedCreateLauncherTools.length > 0) {
|
|
2622
|
+
return cachedCreateLauncherTools;
|
|
2623
|
+
}
|
|
2624
|
+
if (loadingCreateLauncherTools) {
|
|
2625
|
+
return loadingCreateLauncherTools;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
loadingCreateLauncherTools = fetch('/api/plugin/menu')
|
|
2629
|
+
.then((res) => {
|
|
2630
|
+
if (!res.ok) {
|
|
2631
|
+
throw new Error(`Failed to load plugin menu: ${res.status}`);
|
|
2632
|
+
}
|
|
2633
|
+
return res.json();
|
|
2634
|
+
})
|
|
2635
|
+
.then((data) => {
|
|
2636
|
+
const menu = data && Array.isArray(data.menu) ? data.menu : [];
|
|
2637
|
+
const tools = mapPluginMenuToCreateLauncherTools(menu);
|
|
2638
|
+
return tools.length > 0 ? tools : createLauncherFallbackTools.slice();
|
|
2639
|
+
})
|
|
2640
|
+
.catch((error) => {
|
|
2641
|
+
console.warn('Falling back to default agents for create launcher modal', error);
|
|
2642
|
+
return createLauncherFallbackTools.slice();
|
|
2643
|
+
})
|
|
2644
|
+
.finally(() => {
|
|
2645
|
+
loadingCreateLauncherTools = null;
|
|
2646
|
+
});
|
|
2647
|
+
|
|
2648
|
+
const tools = await loadingCreateLauncherTools;
|
|
2649
|
+
cachedCreateLauncherTools = tools;
|
|
2650
|
+
return tools;
|
|
2651
|
+
}
|
|
2292
2652
|
|
|
2293
2653
|
function initCreateLauncherFlow() {
|
|
2294
2654
|
const trigger = document.getElementById('create-launcher-button');
|
|
@@ -2305,295 +2665,362 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
2305
2665
|
requestAnimationFrame(openPendingCreateLauncherModal);
|
|
2306
2666
|
}
|
|
2307
2667
|
|
|
2308
|
-
function ensureCreateLauncherModal() {
|
|
2668
|
+
async function ensureCreateLauncherModal() {
|
|
2309
2669
|
if (createLauncherModalInstance) {
|
|
2310
2670
|
return createLauncherModalInstance;
|
|
2311
2671
|
}
|
|
2672
|
+
if (createLauncherModalPromise) {
|
|
2673
|
+
return createLauncherModalPromise;
|
|
2674
|
+
}
|
|
2312
2675
|
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
const modal = document.createElement('div');
|
|
2317
|
-
modal.className = 'create-launcher-modal';
|
|
2318
|
-
modal.setAttribute('role', 'dialog');
|
|
2319
|
-
modal.setAttribute('aria-modal', 'true');
|
|
2676
|
+
createLauncherModalPromise = (async () => {
|
|
2677
|
+
const tools = await getCreateLauncherTools();
|
|
2320
2678
|
|
|
2321
|
-
|
|
2322
|
-
|
|
2679
|
+
const overlay = document.createElement('div');
|
|
2680
|
+
overlay.className = 'modal-overlay create-launcher-modal-overlay';
|
|
2323
2681
|
|
|
2324
|
-
|
|
2325
|
-
|
|
2682
|
+
const modal = document.createElement('div');
|
|
2683
|
+
modal.className = 'create-launcher-modal';
|
|
2684
|
+
modal.setAttribute('role', 'dialog');
|
|
2685
|
+
modal.setAttribute('aria-modal', 'true');
|
|
2326
2686
|
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
headerIcon.className = 'fa-solid fa-wand-magic-sparkles'
|
|
2330
|
-
iconWrapper.appendChild(headerIcon);
|
|
2687
|
+
const header = document.createElement('div');
|
|
2688
|
+
header.className = 'create-launcher-modal-header';
|
|
2331
2689
|
|
|
2332
|
-
|
|
2333
|
-
|
|
2690
|
+
const iconWrapper = document.createElement('div');
|
|
2691
|
+
iconWrapper.className = 'create-launcher-modal-icon';
|
|
2334
2692
|
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2693
|
+
const headerIcon = document.createElement('i');
|
|
2694
|
+
headerIcon.className = 'fa-solid fa-wand-magic-sparkles'
|
|
2695
|
+
iconWrapper.appendChild(headerIcon);
|
|
2338
2696
|
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
description.id = 'create-launcher-modal-description';
|
|
2342
|
-
description.textContent = 'Create a reusable and shareable launcher for any task or any app'
|
|
2697
|
+
const headingStack = document.createElement('div');
|
|
2698
|
+
headingStack.className = 'create-launcher-modal-headings';
|
|
2343
2699
|
|
|
2344
|
-
|
|
2345
|
-
|
|
2700
|
+
const title = document.createElement('h3');
|
|
2701
|
+
title.id = 'create-launcher-modal-title';
|
|
2702
|
+
title.textContent = 'Create';
|
|
2346
2703
|
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2704
|
+
const description = document.createElement('p');
|
|
2705
|
+
description.className = 'create-launcher-modal-description';
|
|
2706
|
+
description.id = 'create-launcher-modal-description';
|
|
2707
|
+
description.textContent = 'Create a reusable and shareable launcher for any task or any app'
|
|
2351
2708
|
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
promptLabel.textContent = 'What do you want to do?';
|
|
2709
|
+
modal.setAttribute('aria-labelledby', title.id);
|
|
2710
|
+
modal.setAttribute('aria-describedby', description.id);
|
|
2355
2711
|
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2712
|
+
headingStack.appendChild(title);
|
|
2713
|
+
headingStack.appendChild(description);
|
|
2714
|
+
header.appendChild(iconWrapper);
|
|
2715
|
+
header.appendChild(headingStack);
|
|
2360
2716
|
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2717
|
+
const promptLabel = document.createElement('label');
|
|
2718
|
+
promptLabel.className = 'create-launcher-modal-label';
|
|
2719
|
+
promptLabel.textContent = 'What do you want to do?';
|
|
2364
2720
|
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2721
|
+
const promptTextarea = document.createElement('textarea');
|
|
2722
|
+
promptTextarea.className = 'create-launcher-modal-textarea';
|
|
2723
|
+
promptTextarea.placeholder = 'Examples: "a 1-click launcher for ComfyUI", "I want to change file format", "I want to clone a website to run locally", etc. (Leave empty to decide later)';
|
|
2724
|
+
promptLabel.appendChild(promptTextarea);
|
|
2368
2725
|
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2726
|
+
const templateWrapper = document.createElement('div');
|
|
2727
|
+
templateWrapper.className = 'create-launcher-modal-template';
|
|
2728
|
+
templateWrapper.style.display = 'none';
|
|
2372
2729
|
|
|
2373
|
-
|
|
2374
|
-
|
|
2730
|
+
const templateTitle = document.createElement('div');
|
|
2731
|
+
templateTitle.className = 'create-launcher-modal-template-title';
|
|
2732
|
+
templateTitle.textContent = 'Template variables';
|
|
2375
2733
|
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2734
|
+
const templateDescription = document.createElement('p');
|
|
2735
|
+
templateDescription.className = 'create-launcher-modal-template-description';
|
|
2736
|
+
templateDescription.textContent = 'Fill in each variable below before creating your launcher.';
|
|
2379
2737
|
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
folderLabel.textContent = 'name';
|
|
2738
|
+
const templateFields = document.createElement('div');
|
|
2739
|
+
templateFields.className = 'create-launcher-modal-template-fields';
|
|
2383
2740
|
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
folderInput.className = 'create-launcher-modal-input';
|
|
2388
|
-
folderLabel.appendChild(folderInput);
|
|
2741
|
+
templateWrapper.appendChild(templateTitle);
|
|
2742
|
+
templateWrapper.appendChild(templateDescription);
|
|
2743
|
+
templateWrapper.appendChild(templateFields);
|
|
2389
2744
|
|
|
2745
|
+
const folderLabel = document.createElement('label');
|
|
2746
|
+
folderLabel.className = 'create-launcher-modal-label';
|
|
2747
|
+
folderLabel.textContent = 'name';
|
|
2390
2748
|
|
|
2391
|
-
|
|
2392
|
-
|
|
2749
|
+
const folderInput = document.createElement('input');
|
|
2750
|
+
folderInput.type = 'text';
|
|
2751
|
+
folderInput.placeholder = 'example: my-launcher';
|
|
2752
|
+
folderInput.className = 'create-launcher-modal-input';
|
|
2753
|
+
folderLabel.appendChild(folderInput);
|
|
2393
2754
|
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
toolTitle.textContent = 'Choose AI tool';
|
|
2755
|
+
const toolWrapper = document.createElement('div');
|
|
2756
|
+
toolWrapper.className = 'create-launcher-modal-tools';
|
|
2397
2757
|
|
|
2398
|
-
|
|
2399
|
-
|
|
2758
|
+
const toolTitle = document.createElement('div');
|
|
2759
|
+
toolTitle.className = 'create-launcher-modal-tools-title';
|
|
2760
|
+
toolTitle.textContent = 'Select Agent';
|
|
2400
2761
|
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
{ value: 'codex', label: 'OpenAI Codex', iconSrc: '/asset/plugin/code/codex/openai.webp', defaultChecked: false },
|
|
2404
|
-
{ value: 'gemini', label: 'Google Gemini CLI', iconSrc: '/asset/plugin/code/gemini/gemini.jpeg', defaultChecked: false }
|
|
2405
|
-
];
|
|
2762
|
+
const toolOptions = document.createElement('div');
|
|
2763
|
+
toolOptions.className = 'create-launcher-modal-tools-options';
|
|
2406
2764
|
|
|
2407
|
-
|
|
2765
|
+
const toolEntries = [];
|
|
2766
|
+
const defaultToolIndex = tools.findIndex((tool) => tool.isDefault);
|
|
2767
|
+
const initialSelectionIndex = defaultToolIndex >= 0 ? defaultToolIndex : (tools.length > 0 ? 0 : -1);
|
|
2408
2768
|
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2769
|
+
const groupedTools = tools.reduce((acc, tool, index) => {
|
|
2770
|
+
const category = tool.category || 'CLI';
|
|
2771
|
+
if (!acc.has(category)) {
|
|
2772
|
+
acc.set(category, []);
|
|
2773
|
+
}
|
|
2774
|
+
acc.get(category).push({ tool, index });
|
|
2775
|
+
return acc;
|
|
2776
|
+
}, new Map());
|
|
2777
|
+
|
|
2778
|
+
const categoryOrder = ['CLI', 'IDE'];
|
|
2779
|
+
const orderedGroups = [];
|
|
2780
|
+
categoryOrder.forEach((cat) => {
|
|
2781
|
+
if (groupedTools.has(cat)) {
|
|
2782
|
+
orderedGroups.push([cat, groupedTools.get(cat)]);
|
|
2783
|
+
groupedTools.delete(cat);
|
|
2784
|
+
}
|
|
2785
|
+
});
|
|
2786
|
+
groupedTools.forEach((value, key) => {
|
|
2787
|
+
orderedGroups.push([key, value]);
|
|
2788
|
+
});
|
|
2412
2789
|
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
radio.value = value;
|
|
2417
|
-
if (defaultChecked) {
|
|
2418
|
-
radio.checked = true;
|
|
2419
|
-
}
|
|
2790
|
+
orderedGroups.forEach(([category, entries]) => {
|
|
2791
|
+
const group = document.createElement('div');
|
|
2792
|
+
group.className = 'create-launcher-modal-tools-group';
|
|
2420
2793
|
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
option.appendChild(radio);
|
|
2426
|
-
if (iconSrc) {
|
|
2427
|
-
const icon = document.createElement('img');
|
|
2428
|
-
icon.className = 'create-launcher-modal-tool-icon';
|
|
2429
|
-
icon.src = iconSrc;
|
|
2430
|
-
icon.alt = `${label} icon`;
|
|
2431
|
-
icon.onerror = () => { icon.style.display='none'; }
|
|
2432
|
-
option.appendChild(icon);
|
|
2433
|
-
}
|
|
2434
|
-
option.appendChild(badge);
|
|
2435
|
-
toolOptions.appendChild(option);
|
|
2436
|
-
toolEntries.push({ input: radio, container: option });
|
|
2437
|
-
radio.addEventListener('change', () => {
|
|
2438
|
-
updateToolSelections(toolEntries);
|
|
2439
|
-
});
|
|
2440
|
-
});
|
|
2794
|
+
const heading = document.createElement('div');
|
|
2795
|
+
heading.className = 'create-launcher-modal-tools-group-title';
|
|
2796
|
+
heading.textContent = category;
|
|
2797
|
+
group.appendChild(heading);
|
|
2441
2798
|
|
|
2442
|
-
|
|
2443
|
-
|
|
2799
|
+
const groupList = document.createElement('div');
|
|
2800
|
+
groupList.className = 'create-launcher-modal-tools-group-options';
|
|
2444
2801
|
|
|
2445
|
-
|
|
2446
|
-
|
|
2802
|
+
const sortedEntries = entries.slice().sort((a, b) => {
|
|
2803
|
+
const nameA = (a.tool && a.tool.label ? a.tool.label : '').toLowerCase();
|
|
2804
|
+
const nameB = (b.tool && b.tool.label ? b.tool.label : '').toLowerCase();
|
|
2805
|
+
if (nameA < nameB) return -1;
|
|
2806
|
+
if (nameA > nameB) return 1;
|
|
2807
|
+
return 0;
|
|
2808
|
+
});
|
|
2447
2809
|
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
actions.appendChild(cancelButton);
|
|
2462
|
-
actions.appendChild(confirmButton);
|
|
2463
|
-
|
|
2464
|
-
const advancedLink = document.createElement('a');
|
|
2465
|
-
advancedLink.className = 'create-launcher-modal-advanced';
|
|
2466
|
-
advancedLink.href = '/init';
|
|
2467
|
-
advancedLink.textContent = 'Or, try advanced options';
|
|
2468
|
-
|
|
2469
|
-
const bookmarkletLink = document.createElement('a');
|
|
2470
|
-
bookmarkletLink.className = 'create-launcher-modal-advanced secondary';
|
|
2471
|
-
bookmarkletLink.href = '/bookmarklet';
|
|
2472
|
-
bookmarkletLink.target = '_blank';
|
|
2473
|
-
bookmarkletLink.setAttribute("features", "browser")
|
|
2474
|
-
bookmarkletLink.rel = 'noopener';
|
|
2475
|
-
bookmarkletLink.textContent = 'Add 1-click bookmarklet';
|
|
2476
|
-
|
|
2477
|
-
const linkRow = document.createElement('div');
|
|
2478
|
-
linkRow.className = 'create-launcher-modal-links';
|
|
2479
|
-
linkRow.appendChild(advancedLink);
|
|
2480
|
-
linkRow.appendChild(bookmarkletLink);
|
|
2481
|
-
|
|
2482
|
-
modal.appendChild(header);
|
|
2483
|
-
modal.appendChild(promptLabel);
|
|
2484
|
-
modal.appendChild(templateWrapper);
|
|
2485
|
-
modal.appendChild(folderLabel);
|
|
2486
|
-
modal.appendChild(toolWrapper);
|
|
2487
|
-
modal.appendChild(error);
|
|
2488
|
-
modal.appendChild(actions);
|
|
2489
|
-
modal.appendChild(linkRow);
|
|
2490
|
-
overlay.appendChild(modal);
|
|
2491
|
-
document.body.appendChild(overlay);
|
|
2810
|
+
sortedEntries.forEach(({ tool, index }) => {
|
|
2811
|
+
const option = document.createElement('label');
|
|
2812
|
+
option.className = 'create-launcher-modal-tool';
|
|
2813
|
+
|
|
2814
|
+
const radio = document.createElement('input');
|
|
2815
|
+
radio.type = 'radio';
|
|
2816
|
+
radio.name = 'create-launcher-tool';
|
|
2817
|
+
radio.value = tool.value;
|
|
2818
|
+
radio.dataset.agentLabel = tool.label;
|
|
2819
|
+
radio.dataset.agentCategory = category;
|
|
2820
|
+
if (tool.href) {
|
|
2821
|
+
radio.dataset.agentHref = tool.href;
|
|
2822
|
+
}
|
|
2492
2823
|
|
|
2493
|
-
|
|
2494
|
-
|
|
2824
|
+
if (index === initialSelectionIndex) {
|
|
2825
|
+
radio.checked = true;
|
|
2826
|
+
}
|
|
2495
2827
|
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2828
|
+
const badge = document.createElement('span');
|
|
2829
|
+
badge.className = 'create-launcher-modal-tool-label';
|
|
2830
|
+
badge.textContent = tool.label;
|
|
2831
|
+
|
|
2832
|
+
option.appendChild(radio);
|
|
2833
|
+
if (tool.iconSrc) {
|
|
2834
|
+
const icon = document.createElement('img');
|
|
2835
|
+
icon.className = 'create-launcher-modal-tool-icon';
|
|
2836
|
+
icon.src = tool.iconSrc;
|
|
2837
|
+
icon.alt = `${tool.label} icon`;
|
|
2838
|
+
icon.onerror = () => { icon.style.display = 'none'; };
|
|
2839
|
+
option.appendChild(icon);
|
|
2840
|
+
}
|
|
2841
|
+
option.appendChild(badge);
|
|
2842
|
+
groupList.appendChild(option);
|
|
2843
|
+
const entry = { input: radio, container: option, meta: tool };
|
|
2844
|
+
toolEntries.push(entry);
|
|
2845
|
+
radio.addEventListener('change', () => {
|
|
2846
|
+
updateToolSelections(toolEntries);
|
|
2847
|
+
});
|
|
2848
|
+
});
|
|
2500
2849
|
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
newValues.set(name, defaults[name]);
|
|
2504
|
-
} else if (previousValues.has(name)) {
|
|
2505
|
-
newValues.set(name, previousValues.get(name));
|
|
2506
|
-
} else {
|
|
2507
|
-
newValues.set(name, '');
|
|
2508
|
-
}
|
|
2850
|
+
group.appendChild(groupList);
|
|
2851
|
+
toolOptions.appendChild(group);
|
|
2509
2852
|
});
|
|
2510
2853
|
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
return;
|
|
2854
|
+
if (!toolEntries.length) {
|
|
2855
|
+
const emptyState = document.createElement('div');
|
|
2856
|
+
emptyState.className = 'create-launcher-modal-tools-empty';
|
|
2857
|
+
emptyState.textContent = 'No agents available.';
|
|
2858
|
+
toolOptions.appendChild(emptyState);
|
|
2517
2859
|
}
|
|
2518
2860
|
|
|
2519
|
-
|
|
2861
|
+
toolWrapper.appendChild(toolTitle);
|
|
2862
|
+
toolWrapper.appendChild(toolOptions);
|
|
2863
|
+
|
|
2864
|
+
const error = document.createElement('div');
|
|
2865
|
+
error.className = 'create-launcher-modal-error';
|
|
2866
|
+
|
|
2867
|
+
const actions = document.createElement('div');
|
|
2868
|
+
actions.className = 'create-launcher-modal-actions';
|
|
2869
|
+
|
|
2870
|
+
const cancelButton = document.createElement('button');
|
|
2871
|
+
cancelButton.type = 'button';
|
|
2872
|
+
cancelButton.className = 'create-launcher-modal-button cancel';
|
|
2873
|
+
cancelButton.textContent = 'Cancel';
|
|
2874
|
+
|
|
2875
|
+
const confirmButton = document.createElement('button');
|
|
2876
|
+
confirmButton.type = 'button';
|
|
2877
|
+
confirmButton.className = 'create-launcher-modal-button confirm';
|
|
2878
|
+
confirmButton.textContent = 'Create';
|
|
2879
|
+
|
|
2880
|
+
actions.appendChild(cancelButton);
|
|
2881
|
+
actions.appendChild(confirmButton);
|
|
2882
|
+
|
|
2883
|
+
const advancedLink = document.createElement('a');
|
|
2884
|
+
advancedLink.className = 'create-launcher-modal-advanced';
|
|
2885
|
+
advancedLink.href = '/init';
|
|
2886
|
+
advancedLink.textContent = 'Or, try advanced options';
|
|
2887
|
+
|
|
2888
|
+
const bookmarkletLink = document.createElement('a');
|
|
2889
|
+
bookmarkletLink.className = 'create-launcher-modal-advanced secondary';
|
|
2890
|
+
bookmarkletLink.href = '/bookmarklet';
|
|
2891
|
+
bookmarkletLink.target = '_blank';
|
|
2892
|
+
bookmarkletLink.setAttribute('features', 'browser');
|
|
2893
|
+
bookmarkletLink.rel = 'noopener';
|
|
2894
|
+
bookmarkletLink.textContent = 'Add 1-click bookmarklet';
|
|
2895
|
+
|
|
2896
|
+
const linkRow = document.createElement('div');
|
|
2897
|
+
linkRow.className = 'create-launcher-modal-links';
|
|
2898
|
+
linkRow.appendChild(advancedLink);
|
|
2899
|
+
linkRow.appendChild(bookmarkletLink);
|
|
2900
|
+
|
|
2901
|
+
modal.appendChild(header);
|
|
2902
|
+
modal.appendChild(promptLabel);
|
|
2903
|
+
modal.appendChild(templateWrapper);
|
|
2904
|
+
modal.appendChild(folderLabel);
|
|
2905
|
+
modal.appendChild(toolWrapper);
|
|
2906
|
+
modal.appendChild(error);
|
|
2907
|
+
modal.appendChild(actions);
|
|
2908
|
+
modal.appendChild(linkRow);
|
|
2909
|
+
overlay.appendChild(modal);
|
|
2910
|
+
document.body.appendChild(overlay);
|
|
2911
|
+
|
|
2912
|
+
let folderEditedByUser = false;
|
|
2913
|
+
let templateValues = new Map();
|
|
2914
|
+
|
|
2915
|
+
function syncTemplateFields(promptText, defaults = {}) {
|
|
2916
|
+
const variableNames = extractTemplateVariableNames(promptText);
|
|
2917
|
+
const previousValues = templateValues;
|
|
2918
|
+
const newValues = new Map();
|
|
2919
|
+
|
|
2920
|
+
variableNames.forEach((name) => {
|
|
2921
|
+
if (Object.prototype.hasOwnProperty.call(defaults, name) && defaults[name] !== undefined) {
|
|
2922
|
+
newValues.set(name, defaults[name]);
|
|
2923
|
+
} else if (previousValues.has(name)) {
|
|
2924
|
+
newValues.set(name, previousValues.get(name));
|
|
2925
|
+
} else {
|
|
2926
|
+
newValues.set(name, '');
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2520
2929
|
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
field.className = 'create-launcher-modal-template-field';
|
|
2930
|
+
templateValues = newValues;
|
|
2931
|
+
templateFields.innerHTML = '';
|
|
2524
2932
|
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2933
|
+
if (variableNames.length === 0) {
|
|
2934
|
+
templateWrapper.style.display = 'none';
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2528
2937
|
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2938
|
+
templateWrapper.style.display = 'flex';
|
|
2939
|
+
|
|
2940
|
+
variableNames.forEach((name) => {
|
|
2941
|
+
const field = document.createElement('label');
|
|
2942
|
+
field.className = 'create-launcher-modal-template-field';
|
|
2943
|
+
|
|
2944
|
+
const labelText = document.createElement('span');
|
|
2945
|
+
labelText.className = 'create-launcher-modal-template-field-label';
|
|
2946
|
+
labelText.textContent = name;
|
|
2947
|
+
|
|
2948
|
+
const input = document.createElement('input');
|
|
2949
|
+
input.type = 'text';
|
|
2950
|
+
input.className = 'create-launcher-modal-template-input';
|
|
2951
|
+
input.placeholder = `Enter ${name}`;
|
|
2952
|
+
input.value = templateValues.get(name) || '';
|
|
2953
|
+
input.dataset.templateInput = name;
|
|
2954
|
+
input.addEventListener('input', () => {
|
|
2955
|
+
templateValues.set(name, input.value);
|
|
2956
|
+
});
|
|
2957
|
+
|
|
2958
|
+
field.appendChild(labelText);
|
|
2959
|
+
field.appendChild(input);
|
|
2960
|
+
templateFields.appendChild(field);
|
|
2537
2961
|
});
|
|
2962
|
+
}
|
|
2538
2963
|
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
templateFields.appendChild(field);
|
|
2964
|
+
folderInput.addEventListener('input', () => {
|
|
2965
|
+
folderEditedByUser = true;
|
|
2542
2966
|
});
|
|
2543
|
-
}
|
|
2544
2967
|
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2968
|
+
promptTextarea.addEventListener('input', () => {
|
|
2969
|
+
syncTemplateFields(promptTextarea.value);
|
|
2970
|
+
if (folderEditedByUser) return;
|
|
2971
|
+
folderInput.value = generateFolderSuggestion(promptTextarea.value);
|
|
2972
|
+
});
|
|
2548
2973
|
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2974
|
+
cancelButton.addEventListener('click', hideCreateLauncherModal);
|
|
2975
|
+
confirmButton.addEventListener('click', submitCreateLauncherModal);
|
|
2976
|
+
overlay.addEventListener('click', (event) => {
|
|
2977
|
+
if (event.target === overlay) {
|
|
2978
|
+
hideCreateLauncherModal();
|
|
2979
|
+
}
|
|
2980
|
+
});
|
|
2554
2981
|
|
|
2555
|
-
|
|
2556
|
-
confirmButton.addEventListener('click', submitCreateLauncherModal);
|
|
2557
|
-
overlay.addEventListener('click', (event) => {
|
|
2558
|
-
if (event.target === overlay) {
|
|
2982
|
+
advancedLink.addEventListener('click', () => {
|
|
2559
2983
|
hideCreateLauncherModal();
|
|
2560
|
-
}
|
|
2561
|
-
});
|
|
2984
|
+
});
|
|
2562
2985
|
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2986
|
+
bookmarkletLink.addEventListener('click', () => {
|
|
2987
|
+
hideCreateLauncherModal();
|
|
2988
|
+
});
|
|
2566
2989
|
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2990
|
+
createLauncherModalInstance = {
|
|
2991
|
+
overlay,
|
|
2992
|
+
modal,
|
|
2993
|
+
folderInput,
|
|
2994
|
+
promptTextarea,
|
|
2995
|
+
cancelButton,
|
|
2996
|
+
confirmButton,
|
|
2997
|
+
error,
|
|
2998
|
+
toolEntries,
|
|
2999
|
+
toolOptions,
|
|
3000
|
+
toolWrapper,
|
|
3001
|
+
resetFolderTracking() {
|
|
3002
|
+
folderEditedByUser = false;
|
|
3003
|
+
},
|
|
3004
|
+
syncTemplateFields,
|
|
3005
|
+
getTemplateValues() {
|
|
3006
|
+
return new Map(templateValues);
|
|
3007
|
+
},
|
|
3008
|
+
templateFields,
|
|
3009
|
+
markFolderEdited() {
|
|
3010
|
+
folderEditedByUser = true;
|
|
3011
|
+
}
|
|
3012
|
+
};
|
|
2570
3013
|
|
|
2571
|
-
|
|
2572
|
-
overlay,
|
|
2573
|
-
modal,
|
|
2574
|
-
folderInput,
|
|
2575
|
-
promptTextarea,
|
|
2576
|
-
cancelButton,
|
|
2577
|
-
confirmButton,
|
|
2578
|
-
error,
|
|
2579
|
-
toolEntries,
|
|
2580
|
-
// description,
|
|
2581
|
-
resetFolderTracking() {
|
|
2582
|
-
folderEditedByUser = false;
|
|
2583
|
-
},
|
|
2584
|
-
syncTemplateFields,
|
|
2585
|
-
getTemplateValues() {
|
|
2586
|
-
return new Map(templateValues);
|
|
2587
|
-
},
|
|
2588
|
-
templateFields,
|
|
2589
|
-
markFolderEdited() {
|
|
2590
|
-
folderEditedByUser = true;
|
|
2591
|
-
}
|
|
2592
|
-
};
|
|
3014
|
+
updateToolSelections(toolEntries);
|
|
2593
3015
|
|
|
2594
|
-
|
|
3016
|
+
return createLauncherModalInstance;
|
|
3017
|
+
})();
|
|
2595
3018
|
|
|
2596
|
-
|
|
3019
|
+
try {
|
|
3020
|
+
return await createLauncherModalPromise;
|
|
3021
|
+
} finally {
|
|
3022
|
+
createLauncherModalPromise = null;
|
|
3023
|
+
}
|
|
2597
3024
|
}
|
|
2598
3025
|
|
|
2599
3026
|
async function showCreateLauncherModal(defaults = {}) {
|
|
@@ -2607,7 +3034,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
2607
3034
|
return
|
|
2608
3035
|
}
|
|
2609
3036
|
|
|
2610
|
-
const modal = ensureCreateLauncherModal();
|
|
3037
|
+
const modal = await ensureCreateLauncherModal();
|
|
2611
3038
|
|
|
2612
3039
|
modal.error.textContent = '';
|
|
2613
3040
|
modal.resetFolderTracking();
|
|
@@ -2626,8 +3053,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
2626
3053
|
}
|
|
2627
3054
|
|
|
2628
3055
|
const matchingToolEntry = modal.toolEntries.find((entry) => entry.input.value === tool);
|
|
3056
|
+
const defaultToolEntryIndex = modal.toolEntries.findIndex((entry) => entry.meta && entry.meta.isDefault);
|
|
3057
|
+
const fallbackToolIndex = defaultToolEntryIndex >= 0 ? defaultToolEntryIndex : 0;
|
|
2629
3058
|
modal.toolEntries.forEach((entry, index) => {
|
|
2630
|
-
entry.input.checked = matchingToolEntry ? entry === matchingToolEntry : index ===
|
|
3059
|
+
entry.input.checked = matchingToolEntry ? entry === matchingToolEntry : index === fallbackToolIndex;
|
|
2631
3060
|
});
|
|
2632
3061
|
updateToolSelections(modal.toolEntries);
|
|
2633
3062
|
|
|
@@ -2663,14 +3092,22 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
2663
3092
|
}
|
|
2664
3093
|
}
|
|
2665
3094
|
|
|
2666
|
-
function submitCreateLauncherModal() {
|
|
2667
|
-
const modal = ensureCreateLauncherModal();
|
|
3095
|
+
async function submitCreateLauncherModal() {
|
|
3096
|
+
const modal = await ensureCreateLauncherModal();
|
|
2668
3097
|
modal.error.textContent = '';
|
|
2669
3098
|
|
|
2670
3099
|
const folderName = modal.folderInput.value.trim();
|
|
2671
3100
|
const rawPrompt = modal.promptTextarea.value;
|
|
2672
3101
|
const templateValues = modal.getTemplateValues ? modal.getTemplateValues() : new Map();
|
|
2673
|
-
const
|
|
3102
|
+
const selectedEntry = modal.toolEntries.find((entry) => entry.input.checked);
|
|
3103
|
+
const defaultToolEntryIndex = modal.toolEntries.findIndex((entry) => entry.meta && entry.meta.isDefault);
|
|
3104
|
+
const fallbackEntry = defaultToolEntryIndex >= 0 ? modal.toolEntries[defaultToolEntryIndex] : modal.toolEntries[0];
|
|
3105
|
+
const selectedTool = (selectedEntry || fallbackEntry)?.input.value || '';
|
|
3106
|
+
|
|
3107
|
+
if (!selectedTool) {
|
|
3108
|
+
modal.error.textContent = 'Please select an agent.';
|
|
3109
|
+
return;
|
|
3110
|
+
}
|
|
2674
3111
|
|
|
2675
3112
|
if (!folderName) {
|
|
2676
3113
|
modal.error.textContent = 'Please enter a folder name.';
|