pinokiod 3.270.0 → 3.272.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/ansi_stream_tracker.js +115 -0
- package/kernel/api/app/index.js +422 -0
- package/kernel/api/htmlmodal/index.js +94 -0
- package/kernel/app_launcher/index.js +115 -0
- package/kernel/app_launcher/platform/base.js +276 -0
- package/kernel/app_launcher/platform/linux.js +229 -0
- package/kernel/app_launcher/platform/macos.js +163 -0
- package/kernel/app_launcher/platform/unsupported.js +34 -0
- package/kernel/app_launcher/platform/windows.js +247 -0
- package/kernel/bin/conda-meta.js +93 -0
- package/kernel/bin/conda.js +2 -4
- package/kernel/bin/index.js +2 -4
- package/kernel/index.js +9 -2
- package/kernel/peer.js +186 -19
- package/kernel/shell.js +212 -1
- package/package.json +1 -1
- package/server/index.js +491 -6
- package/server/public/common.js +224 -741
- package/server/public/create-launcher.js +754 -0
- package/server/public/htmlmodal.js +292 -0
- package/server/public/logs.js +715 -0
- package/server/public/resizeSync.js +117 -0
- package/server/public/style.css +651 -6
- package/server/public/tab-idle-notifier.js +34 -59
- package/server/public/tab-link-popover.js +7 -10
- package/server/public/terminal-settings.js +723 -9
- package/server/public/terminal_input_utils.js +72 -0
- package/server/public/terminal_key_caption.js +187 -0
- package/server/public/urldropdown.css +120 -3
- package/server/public/xterm-inline-bridge.js +116 -0
- package/server/socket.js +29 -0
- package/server/views/agents.ejs +1 -2
- package/server/views/app.ejs +55 -28
- package/server/views/bookmarklet.ejs +1 -1
- package/server/views/bootstrap.ejs +1 -0
- package/server/views/connect.ejs +1 -2
- package/server/views/create.ejs +63 -0
- package/server/views/editor.ejs +36 -4
- package/server/views/index.ejs +1 -2
- package/server/views/index2.ejs +1 -2
- package/server/views/init/index.ejs +36 -28
- package/server/views/install.ejs +20 -22
- package/server/views/layout.ejs +2 -8
- package/server/views/logs.ejs +155 -0
- package/server/views/mini.ejs +0 -18
- package/server/views/net.ejs +2 -2
- package/server/views/network.ejs +1 -2
- package/server/views/network2.ejs +1 -2
- package/server/views/old_network.ejs +1 -2
- package/server/views/pro.ejs +26 -23
- package/server/views/prototype/index.ejs +30 -34
- package/server/views/screenshots.ejs +1 -2
- package/server/views/settings.ejs +1 -20
- package/server/views/shell.ejs +59 -66
- package/server/views/terminal.ejs +118 -73
- package/server/views/tools.ejs +1 -2
package/server/public/common.js
CHANGED
|
@@ -38,6 +38,22 @@ function pinokioBroadcastMessage(payload, targetOrigin = '*', contextWindow = nu
|
|
|
38
38
|
targets = new Set();
|
|
39
39
|
}
|
|
40
40
|
if (targets.size === 0) {
|
|
41
|
+
try {
|
|
42
|
+
const origin = (() => {
|
|
43
|
+
try {
|
|
44
|
+
return ctx.location ? ctx.location.origin : window.location.origin;
|
|
45
|
+
} catch (_) {
|
|
46
|
+
return '*';
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
const event = new MessageEvent('message', {
|
|
50
|
+
data: payload,
|
|
51
|
+
origin,
|
|
52
|
+
source: ctx
|
|
53
|
+
});
|
|
54
|
+
ctx.dispatchEvent(event);
|
|
55
|
+
dispatched = true;
|
|
56
|
+
} catch (_) {}
|
|
41
57
|
return dispatched;
|
|
42
58
|
}
|
|
43
59
|
targets.forEach((target) => {
|
|
@@ -1501,17 +1517,55 @@ if (typeof hotkeys === 'function') {
|
|
|
1501
1517
|
if (typeof window === 'undefined') {
|
|
1502
1518
|
return;
|
|
1503
1519
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1520
|
+
const shouldDeferToTopListener = (() => {
|
|
1521
|
+
const isLikelyMobile = () => {
|
|
1522
|
+
try {
|
|
1523
|
+
if (navigator.userAgentData && typeof navigator.userAgentData.mobile === 'boolean') {
|
|
1524
|
+
if (navigator.userAgentData.mobile) {
|
|
1525
|
+
return true;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
} catch (_) {}
|
|
1529
|
+
try {
|
|
1530
|
+
const ua = (navigator.userAgent || '').toLowerCase();
|
|
1531
|
+
if (ua && /iphone|ipad|ipod|android|mobile/.test(ua)) {
|
|
1532
|
+
return true;
|
|
1533
|
+
}
|
|
1534
|
+
} catch (_) {}
|
|
1535
|
+
try {
|
|
1536
|
+
if (navigator.maxTouchPoints && navigator.maxTouchPoints > 1) {
|
|
1537
|
+
return true;
|
|
1538
|
+
}
|
|
1539
|
+
} catch (_) {}
|
|
1540
|
+
try {
|
|
1541
|
+
if (window.matchMedia && window.matchMedia('(pointer: coarse)').matches) {
|
|
1542
|
+
return true;
|
|
1543
|
+
}
|
|
1544
|
+
} catch (_) {}
|
|
1545
|
+
return false;
|
|
1546
|
+
};
|
|
1547
|
+
try {
|
|
1548
|
+
const topWindow = window.top;
|
|
1549
|
+
const isTop = topWindow === window;
|
|
1550
|
+
if (isTop && document.getElementById('layout-root')) {
|
|
1551
|
+
const topOwnsAudio = isLikelyMobile();
|
|
1552
|
+
try { window.__pinokioTopHandlesNotificationAudio = topOwnsAudio; } catch (_) {}
|
|
1553
|
+
return topOwnsAudio;
|
|
1554
|
+
}
|
|
1555
|
+
if (!isTop && topWindow) {
|
|
1556
|
+
if (typeof topWindow.__pinokioTopHandlesNotificationAudio === 'boolean') {
|
|
1557
|
+
return topWindow.__pinokioTopHandlesNotificationAudio;
|
|
1558
|
+
}
|
|
1559
|
+
if (topWindow.__pinokioTopNotifyListener) {
|
|
1560
|
+
return true;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
} catch (_) {}
|
|
1564
|
+
return false;
|
|
1565
|
+
})();
|
|
1566
|
+
if (shouldDeferToTopListener) {
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1515
1569
|
if (window.__pinokioNotificationAudioInitialized) {
|
|
1516
1570
|
return;
|
|
1517
1571
|
}
|
|
@@ -1966,8 +2020,10 @@ if (typeof hotkeys === 'function') {
|
|
|
1966
2020
|
const style = document.createElement('style');
|
|
1967
2021
|
style.textContent = `
|
|
1968
2022
|
.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}
|
|
1969
|
-
.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:
|
|
1970
|
-
|
|
2023
|
+
.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:500 15px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;box-shadow:0 16px 40px rgba(0,0,0,.38);text-align:center;max-width:200px}
|
|
2024
|
+
.pinokio-connect-msg-title{font-weight:600;font-size:16px;margin-bottom:4px}
|
|
2025
|
+
.pinokio-connect-msg-hint{font-size:13px;opacity:.72}
|
|
2026
|
+
@media (max-width:768px){.pinokio-connect-msg{font-size:14px;padding:12px 16px}}
|
|
1971
2027
|
`;
|
|
1972
2028
|
document.head.appendChild(style);
|
|
1973
2029
|
|
|
@@ -1978,31 +2034,71 @@ if (typeof hotkeys === 'function') {
|
|
|
1978
2034
|
overlay.tabIndex = 0;
|
|
1979
2035
|
const msg = document.createElement('div');
|
|
1980
2036
|
msg.className = 'pinokio-connect-msg';
|
|
1981
|
-
|
|
2037
|
+
const msgTitle = document.createElement('div');
|
|
2038
|
+
msgTitle.className = 'pinokio-connect-msg-title';
|
|
2039
|
+
msgTitle.textContent = 'Tap to connect';
|
|
2040
|
+
const msgHint = document.createElement('div');
|
|
2041
|
+
msgHint.className = 'pinokio-connect-msg-hint';
|
|
2042
|
+
msgHint.textContent = 'To type into the terminal, use the "Input" button.';
|
|
2043
|
+
msg.appendChild(msgTitle);
|
|
2044
|
+
msg.appendChild(msgHint);
|
|
1982
2045
|
overlay.appendChild(msg);
|
|
1983
2046
|
window.__pinokioConnectCurtainInstalled = true;
|
|
1984
2047
|
return overlay;
|
|
1985
2048
|
};
|
|
1986
2049
|
|
|
2050
|
+
const SOUND_PREF_STORAGE_KEY = 'pinokio:idle-sound';
|
|
1987
2051
|
const primeAudio = async () => {
|
|
2052
|
+
// Determine whether the user picked a custom `/sound/...` clip.
|
|
2053
|
+
// Fall back to the built-in chime if no preference exists.
|
|
2054
|
+
const preferCustom = (() => {
|
|
2055
|
+
try {
|
|
2056
|
+
const raw = localStorage.getItem(SOUND_PREF_STORAGE_KEY);
|
|
2057
|
+
if (!raw) return null;
|
|
2058
|
+
const parsed = JSON.parse(raw);
|
|
2059
|
+
const choice = typeof parsed?.choice === 'string' ? parsed.choice.trim() : '';
|
|
2060
|
+
if (choice && choice.startsWith('/sound/')) {
|
|
2061
|
+
return choice;
|
|
2062
|
+
}
|
|
2063
|
+
} catch (_) {}
|
|
2064
|
+
return null;
|
|
2065
|
+
})();
|
|
2066
|
+
|
|
2067
|
+
// Grab or create an Audio element for the chosen asset and prime it.
|
|
2068
|
+
const asset = preferCustom || '/chime.mp3';
|
|
2069
|
+
let audioEl;
|
|
2070
|
+
if (preferCustom) {
|
|
2071
|
+
audioEl = window.__pinokioCustomNotificationAudio;
|
|
2072
|
+
if (!audioEl || audioEl.__pinokioSrc !== preferCustom) {
|
|
2073
|
+
audioEl = new Audio(preferCustom);
|
|
2074
|
+
audioEl.preload = 'auto';
|
|
2075
|
+
audioEl.loop = false;
|
|
2076
|
+
audioEl.__pinokioSrc = preferCustom;
|
|
2077
|
+
window.__pinokioCustomNotificationAudio = audioEl;
|
|
2078
|
+
}
|
|
2079
|
+
} else {
|
|
2080
|
+
audioEl = window.__pinokioChimeAudio;
|
|
2081
|
+
if (!audioEl) {
|
|
2082
|
+
audioEl = new Audio('/chime.mp3');
|
|
2083
|
+
audioEl.preload = 'auto';
|
|
2084
|
+
audioEl.loop = false;
|
|
2085
|
+
audioEl.__pinokioSrc = '/chime.mp3';
|
|
2086
|
+
window.__pinokioChimeAudio = audioEl;
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
const wasMuted = audioEl.muted;
|
|
2091
|
+
audioEl.muted = true;
|
|
2092
|
+
audioEl.currentTime = 0;
|
|
1988
2093
|
try {
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
a.muted = false;
|
|
1995
|
-
window.__pinokioChimeAudio = a;
|
|
1996
|
-
}
|
|
1997
|
-
a.currentTime = 0;
|
|
1998
|
-
await a.play(); // must be called synchronously in gesture handler
|
|
1999
|
-
try { a.pause(); a.currentTime = 0; } catch (_) {}
|
|
2000
|
-
try { window.__pinokioAudioArmed = true; } catch (_) {}
|
|
2001
|
-
return true;
|
|
2002
|
-
} catch (_) {
|
|
2003
|
-
try { window.__pinokioAudioArmed = true; } catch (_) {}
|
|
2004
|
-
return false;
|
|
2094
|
+
await audioEl.play();
|
|
2095
|
+
} finally {
|
|
2096
|
+
try { audioEl.pause(); } catch (_) {}
|
|
2097
|
+
audioEl.currentTime = 0;
|
|
2098
|
+
audioEl.muted = wasMuted;
|
|
2005
2099
|
}
|
|
2100
|
+
try { window.__pinokioAudioArmed = true; } catch (_) {}
|
|
2101
|
+
return true;
|
|
2006
2102
|
};
|
|
2007
2103
|
|
|
2008
2104
|
const setup = () => {
|
|
@@ -2596,23 +2692,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
2596
2692
|
});
|
|
2597
2693
|
}, true);
|
|
2598
2694
|
|
|
2599
|
-
if (document.querySelector("#genlog")) {
|
|
2600
|
-
document.querySelector("#genlog").addEventListener("click", (e) => {
|
|
2601
|
-
e.preventDefault()
|
|
2602
|
-
e.stopPropagation()
|
|
2603
|
-
e.target.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i>'
|
|
2604
|
-
fetch("/pinokio/log", {
|
|
2605
|
-
method: "post",
|
|
2606
|
-
}).then((res) => {
|
|
2607
|
-
let btn = document.querySelector("#genlog")
|
|
2608
|
-
let btn2 = document.querySelector("#downloadlogs")
|
|
2609
|
-
btn2.classList.remove("hidden")
|
|
2610
|
-
btn.classList.add("hidden")
|
|
2611
|
-
btn.innerHTML = '<i class="fa-solid fa-circle-check"></i> Generated!'
|
|
2612
|
-
//btn.classList.add("hidden")
|
|
2613
|
-
})
|
|
2614
|
-
})
|
|
2615
|
-
}
|
|
2616
2695
|
const closeWindowButton = document.querySelector("#close-window");
|
|
2617
2696
|
if (closeWindowButton) {
|
|
2618
2697
|
const isInIframe = (() => {
|
|
@@ -2717,738 +2796,142 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
2717
2796
|
})
|
|
2718
2797
|
}
|
|
2719
2798
|
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
function openPendingCreateLauncherModal() {
|
|
2727
|
-
if (!pendingCreateLauncherDefaults) return;
|
|
2728
|
-
showCreateLauncherModal(pendingCreateLauncherDefaults);
|
|
2729
|
-
pendingCreateLauncherDefaults = null;
|
|
2730
|
-
|
|
2731
|
-
if (!shouldCleanupCreateLauncherQuery) return;
|
|
2732
|
-
shouldCleanupCreateLauncherQuery = false;
|
|
2799
|
+
const createLauncherState = {
|
|
2800
|
+
pendingDefaults: null,
|
|
2801
|
+
shouldCleanupQuery: false,
|
|
2802
|
+
loaderPromise: null,
|
|
2803
|
+
};
|
|
2733
2804
|
|
|
2734
|
-
|
|
2735
|
-
const url = new URL(window.location.href);
|
|
2736
|
-
Array.from(url.searchParams.keys()).forEach((key) => {
|
|
2737
|
-
if (
|
|
2738
|
-
key === 'create' ||
|
|
2739
|
-
key === 'prompt' ||
|
|
2740
|
-
key === 'folder' ||
|
|
2741
|
-
key === 'tool' ||
|
|
2742
|
-
key.startsWith('template.') ||
|
|
2743
|
-
key.startsWith('template_')
|
|
2744
|
-
) {
|
|
2745
|
-
url.searchParams.delete(key);
|
|
2746
|
-
}
|
|
2747
|
-
});
|
|
2748
|
-
window.history.replaceState(null, '', `${url.pathname}${url.search}${url.hash}`);
|
|
2749
|
-
} catch (error) {
|
|
2750
|
-
console.warn('Failed to update history for create launcher params', error);
|
|
2751
|
-
}
|
|
2752
|
-
}
|
|
2753
|
-
|
|
2754
|
-
let createLauncherModalInstance = null;
|
|
2755
|
-
let createLauncherKeydownHandler = null;
|
|
2756
|
-
let createLauncherModalPromise = null;
|
|
2757
|
-
|
|
2758
|
-
const createLauncherFallbackTools = [
|
|
2759
|
-
{
|
|
2760
|
-
value: 'claude',
|
|
2761
|
-
label: 'Claude Code',
|
|
2762
|
-
iconSrc: '/asset/plugin/code/claude/claude.png',
|
|
2763
|
-
isDefault: true,
|
|
2764
|
-
href: '/run/plugin/code/claude/pinokio.js',
|
|
2765
|
-
category: 'CLI',
|
|
2766
|
-
},
|
|
2767
|
-
{
|
|
2768
|
-
value: 'codex',
|
|
2769
|
-
label: 'OpenAI Codex',
|
|
2770
|
-
iconSrc: '/asset/plugin/code/codex/openai.webp',
|
|
2771
|
-
isDefault: false,
|
|
2772
|
-
href: '/run/plugin/code/codex/pinokio.js',
|
|
2773
|
-
category: 'CLI',
|
|
2774
|
-
},
|
|
2775
|
-
{
|
|
2776
|
-
value: 'gemini',
|
|
2777
|
-
label: 'Google Gemini CLI',
|
|
2778
|
-
iconSrc: '/asset/plugin/code/gemini/gemini.jpeg',
|
|
2779
|
-
isDefault: false,
|
|
2780
|
-
href: '/run/plugin/code/gemini/pinokio.js',
|
|
2781
|
-
category: 'CLI',
|
|
2782
|
-
},
|
|
2783
|
-
];
|
|
2784
|
-
|
|
2785
|
-
let cachedCreateLauncherTools = null;
|
|
2786
|
-
let loadingCreateLauncherTools = null;
|
|
2787
|
-
|
|
2788
|
-
function mapPluginMenuToCreateLauncherTools(menu) {
|
|
2789
|
-
if (!Array.isArray(menu)) return [];
|
|
2790
|
-
|
|
2791
|
-
return menu
|
|
2792
|
-
.map((plugin) => {
|
|
2793
|
-
if (!plugin || (!plugin.href && !plugin.link)) {
|
|
2794
|
-
return null;
|
|
2795
|
-
}
|
|
2796
|
-
const href = typeof plugin.href === 'string' ? plugin.href.trim() : '';
|
|
2797
|
-
const label = plugin.title || plugin.text || plugin.name || href || '';
|
|
2798
|
-
|
|
2799
|
-
let slug = '';
|
|
2800
|
-
if (href) {
|
|
2801
|
-
const segments = href.split('/').filter(Boolean);
|
|
2802
|
-
if (segments.length >= 2) {
|
|
2803
|
-
slug = segments[segments.length - 2] || '';
|
|
2804
|
-
}
|
|
2805
|
-
if (!slug && segments.length) {
|
|
2806
|
-
slug = segments[segments.length - 1] || '';
|
|
2807
|
-
}
|
|
2808
|
-
if (slug.endsWith('.js')) {
|
|
2809
|
-
slug = slug.replace(/\.js$/i, '');
|
|
2810
|
-
}
|
|
2811
|
-
}
|
|
2812
|
-
if (!slug && label) {
|
|
2813
|
-
slug = label
|
|
2814
|
-
.toLowerCase()
|
|
2815
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
2816
|
-
.replace(/^-+|-+$/g, '');
|
|
2817
|
-
}
|
|
2818
|
-
const value = slug || href || (typeof plugin.link === 'string' ? plugin.link.trim() : '');
|
|
2819
|
-
if (!value) {
|
|
2820
|
-
return null;
|
|
2821
|
-
}
|
|
2822
|
-
const iconSrc = plugin.image || null;
|
|
2823
|
-
const runs = Array.isArray(plugin.run) ? plugin.run : [];
|
|
2824
|
-
const hasExec = runs.some((step) => step && step.method === 'exec');
|
|
2825
|
-
const category = hasExec ? 'IDE' : 'CLI';
|
|
2826
|
-
return {
|
|
2827
|
-
value,
|
|
2828
|
-
label,
|
|
2829
|
-
iconSrc,
|
|
2830
|
-
isDefault: Boolean(plugin.default === true),
|
|
2831
|
-
href: href || null,
|
|
2832
|
-
category,
|
|
2833
|
-
};
|
|
2834
|
-
})
|
|
2835
|
-
.filter(Boolean);
|
|
2836
|
-
}
|
|
2805
|
+
initializeCreateLauncherIntegration();
|
|
2837
2806
|
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2807
|
+
function initializeCreateLauncherIntegration() {
|
|
2808
|
+
const defaults = parseCreateLauncherDefaults();
|
|
2809
|
+
const triggerExists = document.getElementById('create-launcher-button');
|
|
2810
|
+
if (!triggerExists && !defaults) {
|
|
2811
|
+
return;
|
|
2841
2812
|
}
|
|
2842
|
-
if (
|
|
2843
|
-
|
|
2813
|
+
if (defaults) {
|
|
2814
|
+
createLauncherState.pendingDefaults = defaults;
|
|
2815
|
+
createLauncherState.shouldCleanupQuery = true;
|
|
2844
2816
|
}
|
|
2845
2817
|
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
})
|
|
2853
|
-
.then((data) => {
|
|
2854
|
-
const menu = data && Array.isArray(data.menu) ? data.menu : [];
|
|
2855
|
-
const tools = mapPluginMenuToCreateLauncherTools(menu);
|
|
2856
|
-
return tools.length > 0 ? tools : createLauncherFallbackTools.slice();
|
|
2857
|
-
})
|
|
2858
|
-
.catch((error) => {
|
|
2859
|
-
console.warn('Falling back to default agents for create launcher modal', error);
|
|
2860
|
-
return createLauncherFallbackTools.slice();
|
|
2861
|
-
})
|
|
2862
|
-
.finally(() => {
|
|
2863
|
-
loadingCreateLauncherTools = null;
|
|
2864
|
-
});
|
|
2865
|
-
|
|
2866
|
-
const tools = await loadingCreateLauncherTools;
|
|
2867
|
-
cachedCreateLauncherTools = tools;
|
|
2868
|
-
return tools;
|
|
2869
|
-
}
|
|
2870
|
-
|
|
2871
|
-
function initCreateLauncherFlow() {
|
|
2872
|
-
const trigger = document.getElementById('create-launcher-button');
|
|
2873
|
-
if (!trigger) return;
|
|
2874
|
-
if (trigger.dataset.createLauncherInit === 'true') return;
|
|
2875
|
-
trigger.dataset.createLauncherInit = 'true';
|
|
2876
|
-
|
|
2877
|
-
trigger.addEventListener('click', () => {
|
|
2878
|
-
showCreateLauncherModal();
|
|
2818
|
+
ensureCreateLauncherModule().then((api) => {
|
|
2819
|
+
if (!api) {
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
initCreateLauncherTrigger(api);
|
|
2823
|
+
openPendingCreateLauncherModal(api);
|
|
2879
2824
|
});
|
|
2880
|
-
|
|
2881
|
-
// If we already captured query params that request the modal, open it now that the
|
|
2882
|
-
// trigger has been initialised and the modal can be constructed.
|
|
2883
|
-
requestAnimationFrame(openPendingCreateLauncherModal);
|
|
2884
2825
|
}
|
|
2885
2826
|
|
|
2886
|
-
|
|
2887
|
-
if (
|
|
2888
|
-
return
|
|
2827
|
+
function ensureCreateLauncherModule() {
|
|
2828
|
+
if (window.CreateLauncher) {
|
|
2829
|
+
return Promise.resolve(window.CreateLauncher);
|
|
2889
2830
|
}
|
|
2890
|
-
if (
|
|
2891
|
-
return
|
|
2831
|
+
if (createLauncherState.loaderPromise) {
|
|
2832
|
+
return createLauncherState.loaderPromise;
|
|
2892
2833
|
}
|
|
2893
2834
|
|
|
2894
|
-
|
|
2895
|
-
const
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
modal.setAttribute('role', 'dialog');
|
|
2903
|
-
modal.setAttribute('aria-modal', 'true');
|
|
2904
|
-
|
|
2905
|
-
const header = document.createElement('div');
|
|
2906
|
-
header.className = 'create-launcher-modal-header';
|
|
2907
|
-
|
|
2908
|
-
const iconWrapper = document.createElement('div');
|
|
2909
|
-
iconWrapper.className = 'create-launcher-modal-icon';
|
|
2910
|
-
|
|
2911
|
-
const headerIcon = document.createElement('i');
|
|
2912
|
-
headerIcon.className = 'fa-solid fa-wand-magic-sparkles'
|
|
2913
|
-
iconWrapper.appendChild(headerIcon);
|
|
2914
|
-
|
|
2915
|
-
const headingStack = document.createElement('div');
|
|
2916
|
-
headingStack.className = 'create-launcher-modal-headings';
|
|
2917
|
-
|
|
2918
|
-
const title = document.createElement('h3');
|
|
2919
|
-
title.id = 'create-launcher-modal-title';
|
|
2920
|
-
title.textContent = 'Create';
|
|
2921
|
-
|
|
2922
|
-
const description = document.createElement('p');
|
|
2923
|
-
description.className = 'create-launcher-modal-description';
|
|
2924
|
-
description.id = 'create-launcher-modal-description';
|
|
2925
|
-
description.textContent = 'Create a reusable and shareable launcher for any task or any app'
|
|
2926
|
-
|
|
2927
|
-
modal.setAttribute('aria-labelledby', title.id);
|
|
2928
|
-
modal.setAttribute('aria-describedby', description.id);
|
|
2929
|
-
|
|
2930
|
-
headingStack.appendChild(title);
|
|
2931
|
-
headingStack.appendChild(description);
|
|
2932
|
-
header.appendChild(iconWrapper);
|
|
2933
|
-
header.appendChild(headingStack);
|
|
2934
|
-
|
|
2935
|
-
const closeButton = document.createElement('button');
|
|
2936
|
-
closeButton.type = 'button';
|
|
2937
|
-
closeButton.className = 'create-launcher-modal-close';
|
|
2938
|
-
closeButton.setAttribute('aria-label', 'Close create launcher modal');
|
|
2939
|
-
closeButton.innerHTML = '<i class="fa-solid fa-xmark"></i>';
|
|
2940
|
-
header.appendChild(closeButton);
|
|
2941
|
-
|
|
2942
|
-
const promptLabel = document.createElement('label');
|
|
2943
|
-
promptLabel.className = 'create-launcher-modal-label';
|
|
2944
|
-
promptLabel.textContent = 'What do you want to do?';
|
|
2945
|
-
|
|
2946
|
-
const promptTextarea = document.createElement('textarea');
|
|
2947
|
-
promptTextarea.className = 'create-launcher-modal-textarea';
|
|
2948
|
-
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)';
|
|
2949
|
-
promptLabel.appendChild(promptTextarea);
|
|
2950
|
-
|
|
2951
|
-
const templateWrapper = document.createElement('div');
|
|
2952
|
-
templateWrapper.className = 'create-launcher-modal-template';
|
|
2953
|
-
templateWrapper.style.display = 'none';
|
|
2954
|
-
|
|
2955
|
-
const templateTitle = document.createElement('div');
|
|
2956
|
-
templateTitle.className = 'create-launcher-modal-template-title';
|
|
2957
|
-
templateTitle.textContent = 'Template variables';
|
|
2958
|
-
|
|
2959
|
-
const templateDescription = document.createElement('p');
|
|
2960
|
-
templateDescription.className = 'create-launcher-modal-template-description';
|
|
2961
|
-
templateDescription.textContent = 'Fill in each variable below before creating your launcher.';
|
|
2962
|
-
|
|
2963
|
-
const templateFields = document.createElement('div');
|
|
2964
|
-
templateFields.className = 'create-launcher-modal-template-fields';
|
|
2965
|
-
|
|
2966
|
-
templateWrapper.appendChild(templateTitle);
|
|
2967
|
-
templateWrapper.appendChild(templateDescription);
|
|
2968
|
-
templateWrapper.appendChild(templateFields);
|
|
2969
|
-
|
|
2970
|
-
const folderLabel = document.createElement('label');
|
|
2971
|
-
folderLabel.className = 'create-launcher-modal-label';
|
|
2972
|
-
folderLabel.textContent = 'name';
|
|
2973
|
-
|
|
2974
|
-
const folderInput = document.createElement('input');
|
|
2975
|
-
folderInput.type = 'text';
|
|
2976
|
-
folderInput.placeholder = 'example: my-launcher';
|
|
2977
|
-
folderInput.className = 'create-launcher-modal-input';
|
|
2978
|
-
folderLabel.appendChild(folderInput);
|
|
2979
|
-
|
|
2980
|
-
const toolWrapper = document.createElement('div');
|
|
2981
|
-
toolWrapper.className = 'create-launcher-modal-tools';
|
|
2982
|
-
|
|
2983
|
-
const toolTitle = document.createElement('div');
|
|
2984
|
-
toolTitle.className = 'create-launcher-modal-tools-title';
|
|
2985
|
-
toolTitle.textContent = 'Select Agent';
|
|
2986
|
-
|
|
2987
|
-
const toolOptions = document.createElement('div');
|
|
2988
|
-
toolOptions.className = 'create-launcher-modal-tools-options';
|
|
2989
|
-
|
|
2990
|
-
const toolEntries = [];
|
|
2991
|
-
const defaultToolIndex = tools.findIndex((tool) => tool.isDefault);
|
|
2992
|
-
const initialSelectionIndex = defaultToolIndex >= 0 ? defaultToolIndex : (tools.length > 0 ? 0 : -1);
|
|
2993
|
-
|
|
2994
|
-
const groupedTools = tools.reduce((acc, tool, index) => {
|
|
2995
|
-
const category = tool.category || 'CLI';
|
|
2996
|
-
if (!acc.has(category)) {
|
|
2997
|
-
acc.set(category, []);
|
|
2998
|
-
}
|
|
2999
|
-
acc.get(category).push({ tool, index });
|
|
3000
|
-
return acc;
|
|
3001
|
-
}, new Map());
|
|
3002
|
-
|
|
3003
|
-
const categoryOrder = ['CLI', 'IDE'];
|
|
3004
|
-
const orderedGroups = [];
|
|
3005
|
-
categoryOrder.forEach((cat) => {
|
|
3006
|
-
if (groupedTools.has(cat)) {
|
|
3007
|
-
orderedGroups.push([cat, groupedTools.get(cat)]);
|
|
3008
|
-
groupedTools.delete(cat);
|
|
3009
|
-
}
|
|
3010
|
-
});
|
|
3011
|
-
groupedTools.forEach((value, key) => {
|
|
3012
|
-
orderedGroups.push([key, value]);
|
|
3013
|
-
});
|
|
3014
|
-
|
|
3015
|
-
orderedGroups.forEach(([category, entries]) => {
|
|
3016
|
-
const group = document.createElement('div');
|
|
3017
|
-
group.className = 'create-launcher-modal-tools-group';
|
|
3018
|
-
|
|
3019
|
-
const heading = document.createElement('div');
|
|
3020
|
-
heading.className = 'create-launcher-modal-tools-group-title';
|
|
3021
|
-
heading.textContent = category;
|
|
3022
|
-
group.appendChild(heading);
|
|
3023
|
-
|
|
3024
|
-
const groupList = document.createElement('div');
|
|
3025
|
-
groupList.className = 'create-launcher-modal-tools-group-options';
|
|
3026
|
-
|
|
3027
|
-
const sortedEntries = entries.slice().sort((a, b) => {
|
|
3028
|
-
const nameA = (a.tool && a.tool.label ? a.tool.label : '').toLowerCase();
|
|
3029
|
-
const nameB = (b.tool && b.tool.label ? b.tool.label : '').toLowerCase();
|
|
3030
|
-
if (nameA < nameB) return -1;
|
|
3031
|
-
if (nameA > nameB) return 1;
|
|
3032
|
-
return 0;
|
|
3033
|
-
});
|
|
3034
|
-
|
|
3035
|
-
sortedEntries.forEach(({ tool, index }) => {
|
|
3036
|
-
const option = document.createElement('label');
|
|
3037
|
-
option.className = 'create-launcher-modal-tool';
|
|
3038
|
-
|
|
3039
|
-
const radio = document.createElement('input');
|
|
3040
|
-
radio.type = 'radio';
|
|
3041
|
-
radio.name = 'create-launcher-tool';
|
|
3042
|
-
radio.value = tool.value;
|
|
3043
|
-
radio.dataset.agentLabel = tool.label;
|
|
3044
|
-
radio.dataset.agentCategory = category;
|
|
3045
|
-
if (tool.href) {
|
|
3046
|
-
radio.dataset.agentHref = tool.href;
|
|
3047
|
-
}
|
|
3048
|
-
|
|
3049
|
-
if (index === initialSelectionIndex) {
|
|
3050
|
-
radio.checked = true;
|
|
3051
|
-
}
|
|
3052
|
-
|
|
3053
|
-
const badge = document.createElement('span');
|
|
3054
|
-
badge.className = 'create-launcher-modal-tool-label';
|
|
3055
|
-
badge.textContent = tool.label;
|
|
3056
|
-
|
|
3057
|
-
option.appendChild(radio);
|
|
3058
|
-
if (tool.iconSrc) {
|
|
3059
|
-
const icon = document.createElement('img');
|
|
3060
|
-
icon.className = 'create-launcher-modal-tool-icon';
|
|
3061
|
-
icon.src = tool.iconSrc;
|
|
3062
|
-
icon.alt = `${tool.label} icon`;
|
|
3063
|
-
icon.onerror = () => { icon.style.display = 'none'; };
|
|
3064
|
-
option.appendChild(icon);
|
|
3065
|
-
}
|
|
3066
|
-
option.appendChild(badge);
|
|
3067
|
-
groupList.appendChild(option);
|
|
3068
|
-
const entry = { input: radio, container: option, meta: tool };
|
|
3069
|
-
toolEntries.push(entry);
|
|
3070
|
-
radio.addEventListener('change', () => {
|
|
3071
|
-
updateToolSelections(toolEntries);
|
|
3072
|
-
});
|
|
3073
|
-
});
|
|
3074
|
-
|
|
3075
|
-
group.appendChild(groupList);
|
|
3076
|
-
toolOptions.appendChild(group);
|
|
3077
|
-
});
|
|
3078
|
-
|
|
3079
|
-
if (!toolEntries.length) {
|
|
3080
|
-
const emptyState = document.createElement('div');
|
|
3081
|
-
emptyState.className = 'create-launcher-modal-tools-empty';
|
|
3082
|
-
emptyState.textContent = 'No agents available.';
|
|
3083
|
-
toolOptions.appendChild(emptyState);
|
|
3084
|
-
}
|
|
3085
|
-
|
|
3086
|
-
toolWrapper.appendChild(toolTitle);
|
|
3087
|
-
toolWrapper.appendChild(toolOptions);
|
|
3088
|
-
|
|
3089
|
-
const error = document.createElement('div');
|
|
3090
|
-
error.className = 'create-launcher-modal-error';
|
|
3091
|
-
|
|
3092
|
-
const actions = document.createElement('div');
|
|
3093
|
-
actions.className = 'create-launcher-modal-actions';
|
|
3094
|
-
|
|
3095
|
-
const cancelButton = document.createElement('button');
|
|
3096
|
-
cancelButton.type = 'button';
|
|
3097
|
-
cancelButton.className = 'create-launcher-modal-button cancel';
|
|
3098
|
-
cancelButton.textContent = 'Cancel';
|
|
3099
|
-
|
|
3100
|
-
const confirmButton = document.createElement('button');
|
|
3101
|
-
confirmButton.type = 'button';
|
|
3102
|
-
confirmButton.className = 'create-launcher-modal-button confirm';
|
|
3103
|
-
confirmButton.textContent = 'Create';
|
|
3104
|
-
|
|
3105
|
-
actions.appendChild(cancelButton);
|
|
3106
|
-
actions.appendChild(confirmButton);
|
|
3107
|
-
|
|
3108
|
-
const advancedLink = document.createElement('a');
|
|
3109
|
-
advancedLink.className = 'create-launcher-modal-advanced';
|
|
3110
|
-
advancedLink.href = '/init';
|
|
3111
|
-
advancedLink.textContent = 'Or, try advanced options';
|
|
3112
|
-
|
|
3113
|
-
const bookmarkletLink = document.createElement('a');
|
|
3114
|
-
bookmarkletLink.className = 'create-launcher-modal-advanced secondary';
|
|
3115
|
-
bookmarkletLink.href = '/bookmarklet';
|
|
3116
|
-
bookmarkletLink.target = '_blank';
|
|
3117
|
-
bookmarkletLink.setAttribute('features', 'browser');
|
|
3118
|
-
bookmarkletLink.rel = 'noopener';
|
|
3119
|
-
bookmarkletLink.textContent = 'Add 1-click bookmarklet';
|
|
3120
|
-
|
|
3121
|
-
const linkRow = document.createElement('div');
|
|
3122
|
-
linkRow.className = 'create-launcher-modal-links';
|
|
3123
|
-
linkRow.appendChild(advancedLink);
|
|
3124
|
-
linkRow.appendChild(bookmarkletLink);
|
|
3125
|
-
|
|
3126
|
-
modal.appendChild(header);
|
|
3127
|
-
modal.appendChild(promptLabel);
|
|
3128
|
-
modal.appendChild(templateWrapper);
|
|
3129
|
-
modal.appendChild(folderLabel);
|
|
3130
|
-
modal.appendChild(toolWrapper);
|
|
3131
|
-
modal.appendChild(error);
|
|
3132
|
-
modal.appendChild(actions);
|
|
3133
|
-
modal.appendChild(linkRow);
|
|
3134
|
-
overlay.appendChild(modal);
|
|
3135
|
-
document.body.appendChild(overlay);
|
|
3136
|
-
|
|
3137
|
-
let folderEditedByUser = false;
|
|
3138
|
-
let templateValues = new Map();
|
|
3139
|
-
|
|
3140
|
-
function syncTemplateFields(promptText, defaults = {}) {
|
|
3141
|
-
const variableNames = extractTemplateVariableNames(promptText);
|
|
3142
|
-
const previousValues = templateValues;
|
|
3143
|
-
const newValues = new Map();
|
|
3144
|
-
|
|
3145
|
-
variableNames.forEach((name) => {
|
|
3146
|
-
if (Object.prototype.hasOwnProperty.call(defaults, name) && defaults[name] !== undefined) {
|
|
3147
|
-
newValues.set(name, defaults[name]);
|
|
3148
|
-
} else if (previousValues.has(name)) {
|
|
3149
|
-
newValues.set(name, previousValues.get(name));
|
|
3150
|
-
} else {
|
|
3151
|
-
newValues.set(name, '');
|
|
3152
|
-
}
|
|
3153
|
-
});
|
|
3154
|
-
|
|
3155
|
-
templateValues = newValues;
|
|
3156
|
-
templateFields.innerHTML = '';
|
|
3157
|
-
|
|
3158
|
-
if (variableNames.length === 0) {
|
|
3159
|
-
templateWrapper.style.display = 'none';
|
|
3160
|
-
return;
|
|
3161
|
-
}
|
|
3162
|
-
|
|
3163
|
-
templateWrapper.style.display = 'flex';
|
|
3164
|
-
|
|
3165
|
-
variableNames.forEach((name) => {
|
|
3166
|
-
const field = document.createElement('label');
|
|
3167
|
-
field.className = 'create-launcher-modal-template-field';
|
|
3168
|
-
|
|
3169
|
-
const labelText = document.createElement('span');
|
|
3170
|
-
labelText.className = 'create-launcher-modal-template-field-label';
|
|
3171
|
-
labelText.textContent = name;
|
|
3172
|
-
|
|
3173
|
-
const input = document.createElement('input');
|
|
3174
|
-
input.type = 'text';
|
|
3175
|
-
input.className = 'create-launcher-modal-template-input';
|
|
3176
|
-
input.placeholder = `Enter ${name}`;
|
|
3177
|
-
input.value = templateValues.get(name) || '';
|
|
3178
|
-
input.dataset.templateInput = name;
|
|
3179
|
-
input.addEventListener('input', () => {
|
|
3180
|
-
templateValues.set(name, input.value);
|
|
3181
|
-
});
|
|
3182
|
-
|
|
3183
|
-
field.appendChild(labelText);
|
|
3184
|
-
field.appendChild(input);
|
|
3185
|
-
templateFields.appendChild(field);
|
|
3186
|
-
});
|
|
3187
|
-
}
|
|
3188
|
-
|
|
3189
|
-
folderInput.addEventListener('input', () => {
|
|
3190
|
-
folderEditedByUser = true;
|
|
3191
|
-
});
|
|
3192
|
-
|
|
3193
|
-
promptTextarea.addEventListener('input', () => {
|
|
3194
|
-
syncTemplateFields(promptTextarea.value);
|
|
3195
|
-
if (folderEditedByUser) return;
|
|
3196
|
-
folderInput.value = generateFolderSuggestion(promptTextarea.value);
|
|
3197
|
-
});
|
|
3198
|
-
|
|
3199
|
-
cancelButton.addEventListener('click', hideCreateLauncherModal);
|
|
3200
|
-
closeButton.addEventListener('click', hideCreateLauncherModal);
|
|
3201
|
-
confirmButton.addEventListener('click', submitCreateLauncherModal);
|
|
3202
|
-
|
|
3203
|
-
advancedLink.addEventListener('click', () => {
|
|
3204
|
-
hideCreateLauncherModal();
|
|
3205
|
-
});
|
|
3206
|
-
|
|
3207
|
-
bookmarkletLink.addEventListener('click', () => {
|
|
3208
|
-
hideCreateLauncherModal();
|
|
3209
|
-
});
|
|
3210
|
-
|
|
3211
|
-
createLauncherModalInstance = {
|
|
3212
|
-
overlay,
|
|
3213
|
-
modal,
|
|
3214
|
-
folderInput,
|
|
3215
|
-
promptTextarea,
|
|
3216
|
-
cancelButton,
|
|
3217
|
-
confirmButton,
|
|
3218
|
-
error,
|
|
3219
|
-
toolEntries,
|
|
3220
|
-
toolOptions,
|
|
3221
|
-
toolWrapper,
|
|
3222
|
-
resetFolderTracking() {
|
|
3223
|
-
folderEditedByUser = false;
|
|
3224
|
-
},
|
|
3225
|
-
syncTemplateFields,
|
|
3226
|
-
getTemplateValues() {
|
|
3227
|
-
return new Map(templateValues);
|
|
3228
|
-
},
|
|
3229
|
-
templateFields,
|
|
3230
|
-
markFolderEdited() {
|
|
3231
|
-
folderEditedByUser = true;
|
|
3232
|
-
}
|
|
2835
|
+
createLauncherState.loaderPromise = new Promise((resolve) => {
|
|
2836
|
+
const script = document.createElement('script');
|
|
2837
|
+
script.src = '/create-launcher.js';
|
|
2838
|
+
script.async = true;
|
|
2839
|
+
script.onload = () => resolve(window.CreateLauncher || null);
|
|
2840
|
+
script.onerror = (error) => {
|
|
2841
|
+
console.warn('Failed to load create launcher module', error);
|
|
2842
|
+
resolve(null);
|
|
3233
2843
|
};
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
return createLauncherModalInstance;
|
|
3238
|
-
})();
|
|
3239
|
-
|
|
3240
|
-
try {
|
|
3241
|
-
return await createLauncherModalPromise;
|
|
3242
|
-
} finally {
|
|
3243
|
-
createLauncherModalPromise = null;
|
|
3244
|
-
}
|
|
3245
|
-
}
|
|
3246
|
-
|
|
3247
|
-
async function showCreateLauncherModal(defaults = {}) {
|
|
3248
|
-
|
|
3249
|
-
let response = await fetch("/bundle/dev").then((res) => {
|
|
3250
|
-
return res.json()
|
|
3251
|
-
})
|
|
3252
|
-
if (response.available) {
|
|
3253
|
-
} else {
|
|
3254
|
-
location.href = "/setup/dev?callback=/"
|
|
3255
|
-
return
|
|
3256
|
-
}
|
|
3257
|
-
|
|
3258
|
-
const modal = await ensureCreateLauncherModal();
|
|
3259
|
-
|
|
3260
|
-
modal.error.textContent = '';
|
|
3261
|
-
modal.resetFolderTracking();
|
|
3262
|
-
const { prompt = '', folder = '', tool = '' } = defaults;
|
|
3263
|
-
|
|
3264
|
-
modal.promptTextarea.value = prompt;
|
|
3265
|
-
if (folder) {
|
|
3266
|
-
modal.folderInput.value = folder;
|
|
3267
|
-
if (typeof modal.markFolderEdited === 'function') {
|
|
3268
|
-
modal.markFolderEdited();
|
|
3269
|
-
}
|
|
3270
|
-
} else if (prompt) {
|
|
3271
|
-
modal.folderInput.value = generateFolderSuggestion(prompt);
|
|
3272
|
-
} else {
|
|
3273
|
-
modal.folderInput.value = '';
|
|
3274
|
-
}
|
|
3275
|
-
|
|
3276
|
-
const matchingToolEntry = modal.toolEntries.find((entry) => entry.input.value === tool);
|
|
3277
|
-
const defaultToolEntryIndex = modal.toolEntries.findIndex((entry) => entry.meta && entry.meta.isDefault);
|
|
3278
|
-
const fallbackToolIndex = defaultToolEntryIndex >= 0 ? defaultToolEntryIndex : 0;
|
|
3279
|
-
modal.toolEntries.forEach((entry, index) => {
|
|
3280
|
-
entry.input.checked = matchingToolEntry ? entry === matchingToolEntry : index === fallbackToolIndex;
|
|
3281
|
-
});
|
|
3282
|
-
updateToolSelections(modal.toolEntries);
|
|
3283
|
-
|
|
3284
|
-
modal.syncTemplateFields(modal.promptTextarea.value, defaults.templateValues || {});
|
|
3285
|
-
|
|
3286
|
-
requestAnimationFrame(() => {
|
|
3287
|
-
modal.overlay.classList.add('is-visible');
|
|
3288
|
-
requestAnimationFrame(() => {
|
|
3289
|
-
modal.folderInput.select();
|
|
3290
|
-
modal.promptTextarea.focus();
|
|
3291
|
-
});
|
|
2844
|
+
const target = document.head || document.body || document.documentElement;
|
|
2845
|
+
target.appendChild(script);
|
|
3292
2846
|
});
|
|
3293
2847
|
|
|
3294
|
-
|
|
3295
|
-
if (event.key === 'Escape') {
|
|
3296
|
-
event.preventDefault();
|
|
3297
|
-
hideCreateLauncherModal();
|
|
3298
|
-
} else if (event.key === 'Enter' && event.target === modal.folderInput) {
|
|
3299
|
-
event.preventDefault();
|
|
3300
|
-
submitCreateLauncherModal();
|
|
3301
|
-
}
|
|
3302
|
-
};
|
|
3303
|
-
|
|
3304
|
-
document.addEventListener('keydown', createLauncherKeydownHandler, true);
|
|
2848
|
+
return createLauncherState.loaderPromise;
|
|
3305
2849
|
}
|
|
3306
2850
|
|
|
3307
|
-
function
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
if (createLauncherKeydownHandler) {
|
|
3311
|
-
document.removeEventListener('keydown', createLauncherKeydownHandler, true);
|
|
3312
|
-
createLauncherKeydownHandler = null;
|
|
3313
|
-
}
|
|
3314
|
-
}
|
|
3315
|
-
|
|
3316
|
-
async function submitCreateLauncherModal() {
|
|
3317
|
-
const modal = await ensureCreateLauncherModal();
|
|
3318
|
-
modal.error.textContent = '';
|
|
3319
|
-
|
|
3320
|
-
const folderName = modal.folderInput.value.trim();
|
|
3321
|
-
const rawPrompt = modal.promptTextarea.value;
|
|
3322
|
-
const templateValues = modal.getTemplateValues ? modal.getTemplateValues() : new Map();
|
|
3323
|
-
const selectedEntry = modal.toolEntries.find((entry) => entry.input.checked);
|
|
3324
|
-
const defaultToolEntryIndex = modal.toolEntries.findIndex((entry) => entry.meta && entry.meta.isDefault);
|
|
3325
|
-
const fallbackEntry = defaultToolEntryIndex >= 0 ? modal.toolEntries[defaultToolEntryIndex] : modal.toolEntries[0];
|
|
3326
|
-
const selectedTool = (selectedEntry || fallbackEntry)?.input.value || '';
|
|
3327
|
-
|
|
3328
|
-
if (!selectedTool) {
|
|
3329
|
-
modal.error.textContent = 'Please select an agent.';
|
|
2851
|
+
function initCreateLauncherTrigger(api) {
|
|
2852
|
+
const trigger = document.getElementById('create-launcher-button');
|
|
2853
|
+
if (!trigger) {
|
|
3330
2854
|
return;
|
|
3331
2855
|
}
|
|
3332
|
-
|
|
3333
|
-
if (!folderName) {
|
|
3334
|
-
modal.error.textContent = 'Please enter a folder name.';
|
|
3335
|
-
modal.folderInput.focus();
|
|
2856
|
+
if (trigger.dataset.createLauncherInit === 'true') {
|
|
3336
2857
|
return;
|
|
3337
2858
|
}
|
|
2859
|
+
trigger.dataset.createLauncherInit = 'true';
|
|
2860
|
+
trigger.addEventListener('click', () => {
|
|
2861
|
+
api.showModal();
|
|
2862
|
+
});
|
|
2863
|
+
}
|
|
3338
2864
|
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
modal.folderInput.focus();
|
|
2865
|
+
function openPendingCreateLauncherModal(api) {
|
|
2866
|
+
if (!api || !createLauncherState.pendingDefaults) {
|
|
3342
2867
|
return;
|
|
3343
2868
|
}
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
if (
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
if (!value || value.trim() === '') {
|
|
3350
|
-
missingVariables.push(name);
|
|
3351
|
-
}
|
|
3352
|
-
});
|
|
3353
|
-
|
|
3354
|
-
if (missingVariables.length > 0) {
|
|
3355
|
-
modal.error.textContent = `Please fill in values for: ${missingVariables.join(', ')}`;
|
|
3356
|
-
const targetInput = modal.templateFields?.querySelector(`[data-template-input="${missingVariables[0]}"]`);
|
|
3357
|
-
if (targetInput) {
|
|
3358
|
-
targetInput.focus();
|
|
3359
|
-
} else {
|
|
3360
|
-
modal.promptTextarea.focus();
|
|
3361
|
-
}
|
|
3362
|
-
return;
|
|
3363
|
-
}
|
|
3364
|
-
|
|
3365
|
-
finalPrompt = applyTemplateValues(rawPrompt, templateValues);
|
|
2869
|
+
api.showModal(createLauncherState.pendingDefaults);
|
|
2870
|
+
createLauncherState.pendingDefaults = null;
|
|
2871
|
+
if (createLauncherState.shouldCleanupQuery) {
|
|
2872
|
+
cleanupCreateLauncherParams();
|
|
2873
|
+
createLauncherState.shouldCleanupQuery = false;
|
|
3366
2874
|
}
|
|
3367
|
-
|
|
3368
|
-
const prompt = finalPrompt.trim();
|
|
3369
|
-
|
|
3370
|
-
const url = `/pro?name=${encodeURIComponent(folderName)}&message=${encodeURIComponent(prompt)}&tool=${encodeURIComponent(selectedTool)}`;
|
|
3371
|
-
hideCreateLauncherModal();
|
|
3372
|
-
window.location.href = url;
|
|
3373
2875
|
}
|
|
3374
2876
|
|
|
3375
|
-
function
|
|
3376
|
-
|
|
3377
|
-
|
|
2877
|
+
function parseCreateLauncherDefaults() {
|
|
2878
|
+
try {
|
|
2879
|
+
const params = new URLSearchParams(window.location.search);
|
|
2880
|
+
if (!params.has('create')) {
|
|
2881
|
+
return null;
|
|
2882
|
+
}
|
|
3378
2883
|
|
|
3379
|
-
|
|
3380
|
-
|
|
2884
|
+
const defaults = {};
|
|
2885
|
+
const templateDefaults = {};
|
|
3381
2886
|
|
|
3382
|
-
|
|
3383
|
-
|
|
2887
|
+
const promptParam = params.get('prompt');
|
|
2888
|
+
if (promptParam) defaults.prompt = promptParam.trim();
|
|
3384
2889
|
|
|
3385
|
-
|
|
3386
|
-
|
|
2890
|
+
const folderParam = params.get('folder');
|
|
2891
|
+
if (folderParam) defaults.folder = folderParam.trim();
|
|
3387
2892
|
|
|
3388
|
-
|
|
3389
|
-
|
|
2893
|
+
const toolParam = params.get('tool');
|
|
2894
|
+
if (toolParam) defaults.tool = toolParam.trim();
|
|
3390
2895
|
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
2896
|
+
params.forEach((value, key) => {
|
|
2897
|
+
if (key.startsWith('template.') || key.startsWith('template_')) {
|
|
2898
|
+
const name = key.replace(/^template[._]/, '');
|
|
2899
|
+
if (name) {
|
|
2900
|
+
templateDefaults[name] = value ? value.trim() : '';
|
|
2901
|
+
}
|
|
3396
2902
|
}
|
|
3397
|
-
}
|
|
3398
|
-
});
|
|
3399
|
-
|
|
3400
|
-
if (Object.keys(templateDefaults).length > 0) {
|
|
3401
|
-
defaults.templateValues = templateDefaults;
|
|
3402
|
-
}
|
|
3403
|
-
|
|
3404
|
-
pendingCreateLauncherDefaults = defaults;
|
|
3405
|
-
shouldCleanupCreateLauncherQuery = true;
|
|
3406
|
-
|
|
3407
|
-
requestAnimationFrame(openPendingCreateLauncherModal);
|
|
3408
|
-
}
|
|
3409
|
-
|
|
3410
|
-
function generateFolderSuggestion(prompt) {
|
|
3411
|
-
if (!prompt) return '';
|
|
3412
|
-
return prompt
|
|
3413
|
-
.toLowerCase()
|
|
3414
|
-
.replace(/[^a-z0-9\-\s_]/g, '')
|
|
3415
|
-
.replace(/[\s_]+/g, '-')
|
|
3416
|
-
.replace(/^-+|-+$/g, '')
|
|
3417
|
-
.slice(0, 50);
|
|
3418
|
-
}
|
|
2903
|
+
});
|
|
3419
2904
|
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
if (input.checked) {
|
|
3423
|
-
container.classList.add('selected');
|
|
3424
|
-
} else {
|
|
3425
|
-
container.classList.remove('selected');
|
|
2905
|
+
if (Object.keys(templateDefaults).length > 0) {
|
|
2906
|
+
defaults.templateValues = templateDefaults;
|
|
3426
2907
|
}
|
|
3427
|
-
});
|
|
3428
|
-
}
|
|
3429
2908
|
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
let match;
|
|
3435
|
-
while ((match = regex.exec(template)) !== null) {
|
|
3436
|
-
names.add(match[1]);
|
|
2909
|
+
return defaults;
|
|
2910
|
+
} catch (error) {
|
|
2911
|
+
console.warn('Failed to parse create launcher params', error);
|
|
2912
|
+
return null;
|
|
3437
2913
|
}
|
|
3438
|
-
return Array.from(names);
|
|
3439
2914
|
}
|
|
3440
2915
|
|
|
3441
|
-
function
|
|
3442
|
-
|
|
2916
|
+
function cleanupCreateLauncherParams() {
|
|
2917
|
+
try {
|
|
2918
|
+
const url = new URL(window.location.href);
|
|
2919
|
+
Array.from(url.searchParams.keys()).forEach((key) => {
|
|
2920
|
+
if (
|
|
2921
|
+
key === 'create' ||
|
|
2922
|
+
key === 'prompt' ||
|
|
2923
|
+
key === 'folder' ||
|
|
2924
|
+
key === 'tool' ||
|
|
2925
|
+
key.startsWith('template.') ||
|
|
2926
|
+
key.startsWith('template_')
|
|
2927
|
+
) {
|
|
2928
|
+
url.searchParams.delete(key);
|
|
2929
|
+
}
|
|
2930
|
+
});
|
|
2931
|
+
window.history.replaceState(null, '', `${url.pathname}${url.search}${url.hash}`);
|
|
2932
|
+
} catch (error) {
|
|
2933
|
+
console.warn('Failed to clean up create launcher params', error);
|
|
2934
|
+
}
|
|
3443
2935
|
}
|
|
3444
2936
|
|
|
3445
|
-
function applyTemplateValues(template, values) {
|
|
3446
|
-
if (!template) return '';
|
|
3447
|
-
let result = template;
|
|
3448
|
-
values.forEach((value, name) => {
|
|
3449
|
-
const pattern = new RegExp(`{{\\s*${escapeRegExp(name)}\\s*}}`, 'g');
|
|
3450
|
-
result = result.replace(pattern, value);
|
|
3451
|
-
});
|
|
3452
|
-
return result;
|
|
3453
|
-
}
|
|
3454
2937
|
})
|