Features: - Welcome screen on first run (provider choice before LS starts) - 15+ AI providers (Google Gemini, OpenAI, Anthropic, DeepSeek, Ollama, etc.) - Provider config syncs to endpoints.json for translation proxy - Built-in Node.js translation proxy for non-native backends - Auto-update support, tray integration, URI scheme handler
372 lines
14 KiB
JavaScript
372 lines
14 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
/**
|
|
* Preload script — runs in every BrowserWindow before the page loads.
|
|
* Exposes a minimal, secure API via contextBridge so the renderer can
|
|
* communicate with the main-process auto-updater without nodeIntegration.
|
|
*/
|
|
const electron_1 = require("electron");
|
|
const updaterAPI = {
|
|
onStateChanged: (callback) => {
|
|
const handler = (_event, state) => {
|
|
callback(state);
|
|
};
|
|
electron_1.ipcRenderer.on('updater:state-changed', handler);
|
|
// Return unsubscribe function
|
|
return () => {
|
|
electron_1.ipcRenderer.removeListener('updater:state-changed', handler);
|
|
};
|
|
},
|
|
applyUpdate: () => electron_1.ipcRenderer.invoke('updater:apply'),
|
|
quitAndInstall: () => electron_1.ipcRenderer.invoke('updater:quit-and-install'),
|
|
checkForUpdates: () => electron_1.ipcRenderer.invoke('updater:check-for-updates'),
|
|
};
|
|
const dialogAPI = {
|
|
showOpenDialog: () => electron_1.ipcRenderer.invoke('dialog:open-workspace'),
|
|
};
|
|
const notificationAPI = {
|
|
send: (options) => electron_1.ipcRenderer.invoke('notification:send', options),
|
|
openSystemPreferences: () => electron_1.ipcRenderer.invoke('notification:open-system-preferences'),
|
|
onClicked: (callback) => {
|
|
const handler = (_event, payload) => {
|
|
callback(payload);
|
|
};
|
|
electron_1.ipcRenderer.on('notification:clicked', handler);
|
|
return () => {
|
|
electron_1.ipcRenderer.removeListener('notification:clicked', handler);
|
|
};
|
|
},
|
|
};
|
|
const storageAPI = {
|
|
getItems: () => electron_1.ipcRenderer.invoke('storage:get-items'),
|
|
updateItems: (changes) => electron_1.ipcRenderer.invoke('storage:update-items', changes),
|
|
onChanged: (callback) => {
|
|
const handler = (_event, changes) => {
|
|
callback(changes);
|
|
};
|
|
electron_1.ipcRenderer.on('storage:changed', handler);
|
|
return () => {
|
|
electron_1.ipcRenderer.removeListener('storage:changed', handler);
|
|
};
|
|
},
|
|
};
|
|
const logsAPI = {
|
|
getElectronLogs: () => electron_1.ipcRenderer.invoke('logs:electron'),
|
|
};
|
|
const extensionsAPI = {
|
|
sendAuthorities: (authoritiesMap) => electron_1.ipcRenderer.invoke('extensions:send-authorities', authoritiesMap),
|
|
};
|
|
const deepLinkAPI = {
|
|
onDeepLink: (callback) => {
|
|
const handler = (_event, url) => {
|
|
callback(url);
|
|
};
|
|
electron_1.ipcRenderer.on('deep-link', handler);
|
|
return () => {
|
|
electron_1.ipcRenderer.removeListener('deep-link', handler);
|
|
};
|
|
},
|
|
getStoredDeepLink: () => electron_1.ipcRenderer.invoke('deep-link:get-stored'),
|
|
// Provider API
|
|
provider: {
|
|
getActive: () => electron_1.ipcRenderer.invoke('provider:get-active'),
|
|
getAll: () => electron_1.ipcRenderer.invoke('provider:get-all'),
|
|
openSettings: () => electron_1.ipcRenderer.invoke('provider:open-settings'),
|
|
onOpenSettings: (handler) => {
|
|
electron_1.ipcRenderer.on('provider:open-settings', handler);
|
|
return () => electron_1.ipcRenderer.removeListener('provider:open-settings', handler);
|
|
},
|
|
},
|
|
};
|
|
const agentAPI = {
|
|
updateActiveAgentCount: (count) => electron_1.ipcRenderer.invoke('agent:update-active-count', count),
|
|
};
|
|
const electronNativeAPI = {
|
|
getZoomLevel: () => electron_1.webFrame.getZoomFactor(),
|
|
setTitleBarOverlay: (options) => electron_1.ipcRenderer.invoke('window:set-title-bar-overlay', options),
|
|
minimize: () => electron_1.ipcRenderer.invoke('window:minimize'),
|
|
maximize: () => electron_1.ipcRenderer.invoke('window:maximize'),
|
|
unmaximize: () => electron_1.ipcRenderer.invoke('window:unmaximize'),
|
|
isMaximized: () => electron_1.ipcRenderer.invoke('window:is-maximized'),
|
|
close: () => electron_1.ipcRenderer.invoke('window:close'),
|
|
toggleDevTools: () => electron_1.ipcRenderer.invoke('window:toggle-devtools'),
|
|
zoomIn: () => {
|
|
const current = electron_1.webFrame.getZoomLevel();
|
|
electron_1.webFrame.setZoomLevel(current + 0.5);
|
|
},
|
|
zoomOut: () => {
|
|
const current = electron_1.webFrame.getZoomLevel();
|
|
electron_1.webFrame.setZoomLevel(current - 0.5);
|
|
},
|
|
resetZoom: () => {
|
|
electron_1.webFrame.setZoomLevel(0);
|
|
},
|
|
openExternal: (url) => electron_1.ipcRenderer.invoke('shell:open-external', url),
|
|
};
|
|
electron_1.contextBridge.exposeInMainWorld('electronUpdater', updaterAPI);
|
|
electron_1.contextBridge.exposeInMainWorld('dialog', dialogAPI);
|
|
electron_1.contextBridge.exposeInMainWorld('nativeNotifications', notificationAPI);
|
|
electron_1.contextBridge.exposeInMainWorld('nativeStorage', storageAPI);
|
|
electron_1.contextBridge.exposeInMainWorld('logs', logsAPI);
|
|
electron_1.contextBridge.exposeInMainWorld('extensions', extensionsAPI);
|
|
electron_1.contextBridge.exposeInMainWorld('deepLink', deepLinkAPI);
|
|
electron_1.contextBridge.exposeInMainWorld('agent', agentAPI);
|
|
electron_1.contextBridge.exposeInMainWorld('electronNative', electronNativeAPI);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Native In-UI AI Provider Switcher
|
|
// ---------------------------------------------------------------------------
|
|
async function fetchEndpoints() {
|
|
try {
|
|
const res = await fetch('http://127.0.0.1:48080/codex/list-endpoints');
|
|
const data = await res.json();
|
|
return data;
|
|
} catch (e) {
|
|
console.error('Failed to fetch endpoints:', e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function switchEndpoint(name) {
|
|
try {
|
|
const res = await fetch('http://127.0.0.1:48080/codex/switch-endpoint', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name })
|
|
});
|
|
const data = await res.json();
|
|
return data.success;
|
|
} catch (e) {
|
|
console.error('Failed to switch endpoint:', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function setupAiProviderSwitcher() {
|
|
if (window.self !== window.top) return;
|
|
if (document.getElementById('codex-ai-switcher-container')) return;
|
|
|
|
setTimeout(async () => {
|
|
const data = await fetchEndpoints();
|
|
if (!data || !data.endpoints || data.endpoints.length === 0) {
|
|
console.log('[Codex] No endpoints found or proxy not running.');
|
|
return;
|
|
}
|
|
|
|
const container = document.createElement('div');
|
|
container.id = 'codex-ai-switcher-container';
|
|
container.style.cssText = `
|
|
position: fixed;
|
|
top: 8px;
|
|
right: 80px;
|
|
z-index: 999999;
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
user-select: none;
|
|
`;
|
|
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
#codex-ai-btn {
|
|
background: rgba(30, 30, 35, 0.65);
|
|
backdrop-filter: blur(20px) saturate(180%);
|
|
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
border-radius: 18px;
|
|
padding: 6px 12px;
|
|
color: rgba(255, 255, 255, 0.9);
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
#codex-ai-btn:hover {
|
|
background: rgba(40, 40, 45, 0.8);
|
|
border-color: rgba(255, 255, 255, 0.25);
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.25);
|
|
}
|
|
#codex-ai-btn:active {
|
|
transform: translateY(0);
|
|
}
|
|
#codex-ai-indicator {
|
|
width: 7px;
|
|
height: 7px;
|
|
border-radius: 50%;
|
|
background: #10b981;
|
|
box-shadow: 0 0 8px #10b981;
|
|
display: inline-block;
|
|
animation: codex-pulse 2s infinite;
|
|
}
|
|
@keyframes codex-pulse {
|
|
0% { opacity: 0.6; box-shadow: 0 0 4px #10b981; }
|
|
50% { opacity: 1; box-shadow: 0 0 10px #10b981; }
|
|
100% { opacity: 0.6; box-shadow: 0 0 4px #10b981; }
|
|
}
|
|
#codex-ai-dropdown {
|
|
position: absolute;
|
|
top: calc(100% + 8px);
|
|
right: 0;
|
|
background: rgba(24, 24, 28, 0.95);
|
|
backdrop-filter: blur(24px);
|
|
-webkit-backdrop-filter: blur(24px);
|
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
border-radius: 12px;
|
|
min-width: 200px;
|
|
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
|
|
padding: 6px;
|
|
opacity: 0;
|
|
transform: translateY(-10px) scale(0.95);
|
|
pointer-events: none;
|
|
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
#codex-ai-dropdown.open {
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
pointer-events: auto;
|
|
}
|
|
.codex-ai-item {
|
|
padding: 8px 12px;
|
|
border-radius: 8px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
color: rgba(255, 255, 255, 0.7);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
}
|
|
.codex-ai-item:hover {
|
|
background: rgba(255, 255, 255, 0.08);
|
|
color: #fff;
|
|
}
|
|
.codex-ai-item.active {
|
|
background: linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(79, 70, 229, 0.25));
|
|
border: 1px solid rgba(99, 102, 241, 0.3);
|
|
color: #818cf8;
|
|
}
|
|
.codex-ai-item.active:hover {
|
|
background: linear-gradient(135deg, rgba(99, 102, 241, 0.3), rgba(79, 70, 229, 0.35));
|
|
}
|
|
#codex-ai-toast {
|
|
position: fixed;
|
|
bottom: 24px;
|
|
right: 24px;
|
|
background: rgba(20, 20, 25, 0.85);
|
|
backdrop-filter: blur(16px);
|
|
-webkit-backdrop-filter: blur(16px);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 12px;
|
|
padding: 10px 18px;
|
|
color: #fff;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
transform: translateY(100px);
|
|
opacity: 0;
|
|
transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
z-index: 1000000;
|
|
}
|
|
#codex-ai-toast.show {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
#codex-ai-arrow {
|
|
border: solid rgba(255, 255, 255, 0.6);
|
|
border-width: 0 1.5px 1.5px 0;
|
|
display: inline-block;
|
|
padding: 2.5px;
|
|
transform: rotate(45deg);
|
|
margin-left: 2px;
|
|
transition: transform 0.2s;
|
|
}
|
|
#codex-ai-btn.open-arrow #codex-ai-arrow {
|
|
transform: rotate(-135deg);
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
const btn = document.createElement('div');
|
|
btn.id = 'codex-ai-btn';
|
|
btn.innerHTML = `<span id="codex-ai-indicator"></span><span id="codex-ai-btn-text">${data.active}</span><i id="codex-ai-arrow"></i>`;
|
|
|
|
const dropdown = document.createElement('div');
|
|
dropdown.id = 'codex-ai-dropdown';
|
|
|
|
const updateDropdownItems = (activeName) => {
|
|
dropdown.innerHTML = '';
|
|
data.endpoints.forEach(ep => {
|
|
const item = document.createElement('div');
|
|
item.className = 'codex-ai-item' + (ep.name === activeName ? ' active' : '');
|
|
item.innerHTML = `
|
|
<span>${ep.name}</span>
|
|
<span style="font-size: 10px; opacity: 0.5; font-weight: normal;">${ep.backend_type}</span>
|
|
`;
|
|
item.addEventListener('click', async (e) => {
|
|
e.stopPropagation();
|
|
dropdown.classList.remove('open');
|
|
btn.classList.remove('open-arrow');
|
|
|
|
if (ep.name === activeName) return;
|
|
|
|
const success = await switchEndpoint(ep.name);
|
|
if (success) {
|
|
activeName = ep.name;
|
|
document.getElementById('codex-ai-btn-text').textContent = ep.name;
|
|
updateDropdownItems(ep.name);
|
|
showToast(`Switched provider to ${ep.name}`);
|
|
} else {
|
|
showToast('Failed to switch AI provider');
|
|
}
|
|
});
|
|
dropdown.appendChild(item);
|
|
});
|
|
};
|
|
|
|
updateDropdownItems(data.active);
|
|
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const isOpen = dropdown.classList.toggle('open');
|
|
btn.classList.toggle('open-arrow', isOpen);
|
|
});
|
|
|
|
document.addEventListener('click', () => {
|
|
dropdown.classList.remove('open');
|
|
btn.classList.remove('open-arrow');
|
|
});
|
|
|
|
container.appendChild(btn);
|
|
container.appendChild(dropdown);
|
|
document.body.appendChild(container);
|
|
|
|
const toast = document.createElement('div');
|
|
toast.id = 'codex-ai-toast';
|
|
document.body.appendChild(toast);
|
|
|
|
let toastTimeout = null;
|
|
function showToast(message) {
|
|
toast.textContent = message;
|
|
toast.classList.add('show');
|
|
if (toastTimeout) clearTimeout(toastTimeout);
|
|
toastTimeout = setTimeout(() => {
|
|
toast.classList.remove('show');
|
|
}, 3000);
|
|
}
|
|
|
|
console.log('[Codex] Seamless AI provider switcher injected successfully.');
|
|
}, 1500);
|
|
}
|
|
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
setupAiProviderSwitcher();
|
|
});
|