Add provider setup wizard on first launch

Added first-run provider selection wizard with:

- Google OAuth option (sign in with Google account)
- Custom provider option (choose from 17+ presets)
- 3-step setup wizard UI
- Automatic provider configuration
- Pre-configured Google Antigravity OAuth

Files changed:
- main.js: Added AI provider check and wizard display logic
- preload.js: Added app:launch-main IPC handler
- provider-setup-wizard.html: Complete wizard UI (new)
- app.asar: Repacked with all changes

Features:
- Shows wizard if no AI provider configured
- Google OAuth for easy setup
- 17+ provider presets for custom setup
- Automatic settings configuration
- Professional UI with step indicators
This commit is contained in:
2026-05-22 10:46:29 +00:00
Unverified
parent 0265d58123
commit a87f0dbb4f
4 changed files with 907 additions and 0 deletions

View File

@@ -83,6 +83,77 @@ if (HEADLESS) {
if (!electron_1.app.commandLine.hasSwitch('remote-debugging-port')) { if (!electron_1.app.commandLine.hasSwitch('remote-debugging-port')) {
electron_1.app.commandLine.appendSwitch('remote-debugging-port', '0'); electron_1.app.commandLine.appendSwitch('remote-debugging-port', '0');
} }
// ---------------------------------------------------------------------------
// AI Provider Setup Functions
// ---------------------------------------------------------------------------
let providerSetupWindow = null;
async function checkAIProviderSetup() {
try {
// Check if any provider is configured
const items = await storageManager.getItems();
const hasProvider = items['aiProvider'] && items['aiModel'];
const hasProvidersConfig = items['aiProviders'];
// Return true if setup is complete
return hasProvider || hasProvidersConfig;
} catch (error) {
console.error('Error checking AI provider setup:', error);
return true; // Skip wizard if error
}
}
async function showProviderSetupWizard() {
return new Promise((resolve) => {
const wizardUrl = `file://${__dirname}/provider-setup-wizard.html`;
providerSetupWindow = new electron_1.BrowserWindow({
width: 1000,
height: 800,
title: 'Antigravity - AI Provider Setup',
icon: `${__dirname}/icon.png`,
autoHideMenuBar: true,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: `${__dirname}/preload.js`,
},
});
providerSetupWindow.loadFile(`${__dirname}/provider-setup-wizard.html`);
// Handle setup completion
providerSetupWindow.webContents.on('did-finish-load', () => {
console.log('Provider setup wizard loaded');
});
// Listen for setup completion message
electron_1.ipcMain.once('provider-setup:complete', (event, data) => {
console.log('Provider setup completed:', data);
if (providerSetupWindow) {
providerSetupWindow.close();
providerSetupWindow = null;
}
resolve();
});
// Listen for setup cancelled
electron_1.ipcMain.once('provider-setup:cancelled', () => {
console.log('Provider setup cancelled');
if (providerSetupWindow) {
providerSetupWindow.close();
providerSetupWindow = null;
}
resolve();
});
providerSetupWindow.on('closed', () => {
providerSetupWindow = null;
resolve();
});
});
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Application Lifecycle // Application Lifecycle
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -155,8 +226,27 @@ electron_1.app
pendingDeepLink = null; // Clear after read pendingDeepLink = null; // Clear after read
return link; return link;
}); });
// Check if AI providers are configured - show wizard if not
const aiProviderSetupComplete = await checkAIProviderSetup();
if (!aiProviderSetupComplete && !HEADLESS) {
await showProviderSetupWizard();
return; // Wait for wizard to complete
}
// Handle requests coming from custom schemes // Handle requests coming from custom schemes
(0, customScheme_1.registerCustomSchemeHandlers)(); (0, customScheme_1.registerCustomSchemeHandlers)();
// Handler for launching main app after provider setup
electron_1.ipcMain.handle('app:launch-main', async () => {
// Resume the app startup
hasStartedMainApplication = false;
// Restart the initialization process
await startMainApplication();
});
/**
* Main application startup function
*/
async function startMainApplication() {
// Set About panel options with LS CL // Set About panel options with LS CL
const cl = await (0, languageServer_1.getLsCL)(); const cl = await (0, languageServer_1.getLsCL)();
electron_1.app.setAboutPanelOptions({ electron_1.app.setAboutPanelOptions({

View File

@@ -93,6 +93,10 @@ const electronNativeAPI = {
}, },
openExternal: (url) => electron_1.ipcRenderer.invoke('shell:open-external', url), openExternal: (url) => electron_1.ipcRenderer.invoke('shell:open-external', url),
}; };
const appAPI = {
launchMain: () => electron_1.ipcRenderer.invoke('app:launch-main'),
};
electron_1.contextBridge.exposeInMainWorld('electronUpdater', updaterAPI); electron_1.contextBridge.exposeInMainWorld('electronUpdater', updaterAPI);
electron_1.contextBridge.exposeInMainWorld('dialog', dialogAPI); electron_1.contextBridge.exposeInMainWorld('dialog', dialogAPI);
electron_1.contextBridge.exposeInMainWorld('nativeNotifications', notificationAPI); electron_1.contextBridge.exposeInMainWorld('nativeNotifications', notificationAPI);
@@ -102,3 +106,4 @@ electron_1.contextBridge.exposeInMainWorld('extensions', extensionsAPI);
electron_1.contextBridge.exposeInMainWorld('deepLink', deepLinkAPI); electron_1.contextBridge.exposeInMainWorld('deepLink', deepLinkAPI);
electron_1.contextBridge.exposeInMainWorld('agent', agentAPI); electron_1.contextBridge.exposeInMainWorld('agent', agentAPI);
electron_1.contextBridge.exposeInMainWorld('electronNative', electronNativeAPI); electron_1.contextBridge.exposeInMainWorld('electronNative', electronNativeAPI);
electron_1.contextBridge.exposeInMainWorld('app', appAPI);

View File

@@ -0,0 +1,812 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Antigravity - AI Provider Setup</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
max-width: 900px;
width: 100%;
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40px;
text-align: center;
}
.header h1 {
font-size: 32px;
margin-bottom: 10px;
}
.header p {
opacity: 0.9;
font-size: 16px;
}
.content {
padding: 40px;
}
.step-indicator {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 40px;
}
.step {
display: flex;
align-items: center;
gap: 10px;
color: #999;
}
.step.active {
color: #667eea;
}
.step-number {
width: 32px;
height: 32px;
border-radius: 50%;
background: #e0e0e0;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 14px;
}
.step.active .step-number {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.step.completed .step-number {
background: #4caf50;
color: white;
}
.options-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
margin-bottom: 40px;
}
.option-card {
border: 3px solid #e0e0e0;
border-radius: 16px;
padding: 28px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.option-card:hover {
border-color: #667eea;
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.2);
transform: translateY(-4px);
}
.option-card.selected {
border-color: #667eea;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.05) 0%, rgba(118, 75, 162, 0.05) 100%);
}
.option-icon {
font-size: 48px;
margin-bottom: 16px;
}
.option-title {
font-size: 22px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
}
.option-description {
font-size: 14px;
color: #666;
line-height: 1.6;
}
.option-features {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #e0e0e0;
}
.feature-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: #666;
margin-bottom: 8px;
}
.feature-item::before {
content: "✓";
color: #4caf50;
font-weight: bold;
}
.btn {
padding: 14px 32px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.btn-secondary {
background: #f0f0f0;
color: #333;
}
.btn-secondary:hover {
background: #e0e0e0;
}
.action-buttons {
display: flex;
justify-content: space-between;
gap: 16px;
padding-top: 20px;
border-top: 1px solid #e0e0e0;
}
.action-buttons .btn-group {
display: flex;
gap: 12px;
}
.provider-form {
background: #f8f9fa;
border-radius: 12px;
padding: 28px;
margin-top: 20px;
}
.form-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.form-input {
width: 100%;
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
transition: all 0.2s ease;
}
.form-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.form-select {
width: 100%;
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
background: white;
cursor: pointer;
}
.preset-section {
margin-top: 24px;
}
.preset-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 12px;
margin-top: 16px;
}
.preset-card {
border: 2px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.preset-card:hover {
border-color: #667eea;
background: rgba(102, 126, 234, 0.05);
}
.preset-card.selected {
border-color: #667eea;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
}
.preset-name {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 6px;
}
.preset-models {
font-size: 12px;
color: #666;
}
.success-screen {
text-align: center;
padding: 40px;
}
.success-icon {
font-size: 80px;
margin-bottom: 20px;
}
.success-title {
font-size: 28px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
}
.success-description {
font-size: 16px;
color: #666;
margin-bottom: 30px;
}
.spinner {
display: inline-block;
width: 24px;
height: 24px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.loading-content {
background: white;
padding: 40px;
border-radius: 12px;
text-align: center;
}
.loading-content p {
margin-top: 16px;
font-size: 16px;
color: #666;
}
.hidden {
display: none !important;
}
</style>
</head>
<body>
<div id="loading-overlay" class="loading-overlay hidden">
<div class="loading-content">
<div class="spinner"></div>
<p id="loading-message">Setting up...</p>
</div>
</div>
<div class="container">
<div class="header">
<h1>🤖 Welcome to Antigravity</h1>
<p>Choose how you want to connect to AI providers</p>
</div>
<div class="content">
<!-- Step 1: Choose Setup Method -->
<div id="step1" class="setup-step">
<div class="step-indicator">
<div class="step active">
<div class="step-number">1</div>
<span>Choose Setup</span>
</div>
<div class="step">
<div class="step-number">2</div>
<span>Configure</span>
</div>
<div class="step">
<div class="step-number">3</div>
<span>Complete</span>
</div>
</div>
<div class="options-grid">
<!-- Google OAuth -->
<div class="option-card" onclick="selectOption('google-oauth')" data-option="google-oauth">
<div class="option-icon">🔐</div>
<div class="option-title">Google OAuth</div>
<div class="option-description">
Sign in with your Google account to access Google AI services including Gemini and Antigravity models.
</div>
<div class="option-features">
<div class="feature-item">Easy one-click setup</div>
<div class="feature-item">Access to Gemini models</div>
<div class="feature-item">Antigravity-specific models</div>
<div class="feature-item">No API key needed</div>
</div>
</div>
<!-- Custom Provider -->
<div class="option-card" onclick="selectOption('custom-provider')" data-option="custom-provider">
<div class="option-icon">⚙️</div>
<div class="option-title">Custom Provider</div>
<div class="option-description">
Use your own API key to connect to any AI provider. Choose from 17+ pre-configured providers.
</div>
<div class="option-features">
<div class="feature-item">17+ provider presets</div>
<div class="feature-item">Bring your own API key</div>
<div class="feature-item">Full control over settings</div>
<div class="feature-item">OpenAI, Anthropic, and more</div>
</div>
</div>
</div>
<div class="action-buttons">
<div></div>
<div class="btn-group">
<button class="btn btn-primary" id="next-btn" disabled onclick="goToStep2()">
Next →
</button>
</div>
</div>
</div>
<!-- Step 2: Configure Provider -->
<div id="step2" class="setup-step hidden">
<div class="step-indicator">
<div class="step completed">
<div class="step-number"></div>
<span>Choose Setup</span>
</div>
<div class="step active">
<div class="step-number">2</div>
<span>Configure</span>
</div>
<div class="step">
<div class="step-number">3</div>
<span>Complete</span>
</div>
</div>
<!-- Google OAuth Configuration -->
<div id="google-config" class="hidden">
<h2 style="font-size: 24px; color: #333; margin-bottom: 20px;">🔐 Google Account Setup</h2>
<p style="font-size: 16px; color: #666; margin-bottom: 30px;">
Click the button below to sign in with your Google account. You'll be redirected to Google's OAuth page.
</p>
<div style="text-align: center; margin: 40px 0;">
<button class="btn btn-primary" style="font-size: 18px; padding: 16px 40px;" onclick="startGoogleOAuth()">
🔐 Sign in with Google
</button>
</div>
<div style="background: #f0f7ff; border-left: 4px solid #667eea; padding: 16px; border-radius: 4px; margin: 20px 0;">
<strong>What you'll get:</strong>
<ul style="margin: 12px 0 0 20px; line-height: 1.8;">
<li>Access to Google Gemini models</li>
<li>Antigravity-specific models (antigravity-gemini-3-flash, etc.)</li>
<li>Claude Sonnet via Antigravity</li>
<li>And more...</li>
</ul>
</div>
</div>
<!-- Custom Provider Configuration -->
<div id="custom-config" class="hidden">
<h2 style="font-size: 24px; color: #333; margin-bottom: 20px;">⚙️ Custom Provider Setup</h2>
<div class="preset-section">
<label class="form-label">Choose a Provider Preset</label>
<div class="preset-grid" id="preset-grid">
<!-- Presets will be loaded here -->
</div>
</div>
<div class="provider-form" id="provider-form" style="margin-top: 24px;">
<div class="form-title" id="form-title">Configure Provider</div>
<div class="form-group">
<label class="form-label">Provider Name</label>
<input type="text" id="provider-name" class="form-input" placeholder="My Custom Provider">
</div>
<div class="form-group">
<label class="form-label">API Type</label>
<select id="provider-type" class="form-select">
<option value="openai">OpenAI (GPT-4, GPT-3.5)</option>
<option value="anthropic">Anthropic (Claude)</option>
<option value="google_gemini">Google Gemini</option>
<option value="google_antigravity">Google Antigravity</option>
<option value="ollama">Ollama (Local)</option>
<option value="groq">Groq</option>
<option value="openrouter">OpenRouter</option>
<option value="custom">Custom</option>
</select>
</div>
<div class="form-group">
<label class="form-label">API Endpoint</label>
<input type="text" id="provider-endpoint" class="form-input" placeholder="https://api.openai.com/v1">
</div>
<div class="form-group">
<label class="form-label">API Key</label>
<input type="password" id="provider-apikey" class="form-input" placeholder="sk-...">
</div>
<div class="form-group">
<label class="form-label">Default Model</label>
<input type="text" id="provider-model" class="form-input" placeholder="gpt-4o">
</div>
</div>
</div>
<div class="action-buttons">
<button class="btn btn-secondary" onclick="goToStep1()">
← Back
</button>
<div class="btn-group">
<button class="btn btn-primary" id="setup-btn" onclick="completeSetup()">
Complete Setup
</button>
</div>
</div>
</div>
<!-- Step 3: Success -->
<div id="step3" class="setup-step hidden">
<div class="success-screen">
<div class="success-icon">🎉</div>
<div class="success-title">Setup Complete!</div>
<div class="success-description">
Antigravity is ready to use with your AI provider.
</div>
<button class="btn btn-primary" style="font-size: 18px; padding: 16px 40px;" onclick="launchAntigravity()">
🚀 Launch Antigravity
</button>
</div>
</div>
</div>
</div>
<script>
let selectedOption = null;
let selectedPreset = null;
let providers = [];
// Initialize
async function init() {
try {
// Load available presets
const presets = await window.electron.invoke('ai:get-available-presets');
renderPresets(presets);
// Load existing providers
providers = await window.electron.invoke('ai:get-providers');
} catch (error) {
console.error('Error initializing:', error);
showError('Failed to initialize. Please restart the application.');
}
}
function selectOption(option) {
selectedOption = option;
// Update UI
document.querySelectorAll('.option-card').forEach(card => {
card.classList.remove('selected');
});
document.querySelector(`[data-option="${option}"]`).classList.add('selected');
// Enable next button
document.getElementById('next-btn').disabled = false;
}
function goToStep1() {
document.querySelectorAll('.setup-step').forEach(step => step.classList.add('hidden'));
document.getElementById('step1').classList.remove('hidden');
updateStepIndicators(1);
}
function goToStep2() {
if (!selectedOption) return;
document.querySelectorAll('.setup-step').forEach(step => step.classList.add('hidden'));
document.getElementById('step2').classList.remove('hidden');
updateStepIndicators(2);
// Show appropriate config
document.getElementById('google-config').classList.toggle('hidden', selectedOption !== 'google-oauth');
document.getElementById('custom-config').classList.toggle('hidden', selectedOption !== 'custom-provider');
}
function goToStep3() {
document.querySelectorAll('.setup-step').forEach(step => step.classList.add('hidden'));
document.getElementById('step3').classList.remove('hidden');
updateStepIndicators(3);
}
function updateStepIndicators(currentStep) {
document.querySelectorAll('.step').forEach((step, index) => {
step.classList.remove('active', 'completed');
if (index + 1 < currentStep) {
step.classList.add('completed');
step.querySelector('.step-number').textContent = '✓';
} else if (index + 1 === currentStep) {
step.classList.add('active');
step.querySelector('.step-number').textContent = index + 1;
} else {
step.querySelector('.step-number').textContent = index + 1;
}
});
}
function renderPresets(presets) {
const grid = document.getElementById('preset-grid');
// Group presets by category
const categories = {
'Google': presets.filter(p => p.toLowerCase().includes('google') || p.toLowerCase().includes('gemini') || p.toLowerCase().includes('antigravity')),
'OpenAI': presets.filter(p => p.toLowerCase().includes('openai')),
'Anthropic': presets.filter(p => p.toLowerCase().includes('anthropic')),
'OpenRouter': presets.filter(p => p.toLowerCase().includes('router')),
'Other': presets.filter(p =>
!p.toLowerCase().includes('google') &&
!p.toLowerCase().includes('gemini') &&
!p.toLowerCase().includes('antigravity') &&
!p.toLowerCase().includes('openai') &&
!p.toLowerCase().includes('anthropic') &&
!p.toLowerCase().includes('router')
)
};
grid.innerHTML = Object.entries(categories)
.filter(([name, items]) => items.length > 0)
.map(([category, items]) => `
${items.map(preset => `
<div class="preset-card" onclick="selectPreset('${preset}')" data-preset="${preset}">
<div class="preset-name">${preset}</div>
</div>
`).join('')}
`).join('');
}
function selectPreset(presetName) {
selectedPreset = presetName;
// Update UI
document.querySelectorAll('.preset-card').forEach(card => {
card.classList.remove('selected');
});
document.querySelector(`[data-preset="${presetName}"]`).classList.add('selected');
// Get preset details
window.electron.invoke('ai:get-preset', presetName).then(preset => {
if (preset) {
document.getElementById('form-title').textContent = `Configure: ${presetName}`;
document.getElementById('provider-name').value = presetName;
document.getElementById('provider-type').value = preset.type || 'custom';
document.getElementById('provider-endpoint').value = preset.endpoint || '';
if (preset.models && preset.models.length > 0) {
document.getElementById('provider-model').value = preset.models[0];
}
}
}).catch(err => {
console.error('Error loading preset:', err);
});
}
function startGoogleOAuth() {
showLoading('Starting Google OAuth flow...');
// Add Google Antigravity OAuth provider
window.electron.invoke('ai:add-provider', {
name: 'Google Antigravity (OAuth)',
type: 'google_antigravity',
endpoint: 'https://daily-cloudcode-pa.sandbox.googleapis.com',
apiKey: 'oauth',
models: [
'antigravity-gemini-3-flash',
'antigravity-gemini-3-pro',
'antigravity-gemini-3.1-pro',
'antigravity-claude-sonnet-4-6',
'antigravity-claude-opus-4-6-thinking',
'gemini-2.5-flash',
'gemini-2.5-pro'
],
defaultModel: 'antigravity-gemini-3-flash',
capabilities: ['chat', 'vision', 'tool_use', 'streaming']
}).then(() => {
hideLoading();
showSuccess('Google Antigravity OAuth configured successfully!');
setTimeout(() => goToStep3(), 1500);
}).catch(err => {
hideLoading();
showError('Failed to configure Google OAuth: ' + err.message);
});
}
async function completeSetup() {
if (selectedOption === 'google-oauth') {
startGoogleOAuth();
return;
}
// Custom provider setup
const name = document.getElementById('provider-name').value.trim();
const endpoint = document.getElementById('provider-endpoint').value.trim();
const apiKey = document.getElementById('provider-apikey').value.trim();
const model = document.getElementById('provider-model').value.trim();
const type = document.getElementById('provider-type').value;
if (!name || !endpoint) {
showError('Please fill in all required fields.');
return;
}
showLoading('Setting up your provider...');
try {
await window.electron.invoke('ai:add-provider', {
name: name,
type: type,
endpoint: endpoint,
apiKey: apiKey,
models: model ? [model] : [],
defaultModel: model,
capabilities: ['chat', 'streaming']
});
// Set as default provider
const providers = await window.electron.invoke('ai:get-providers');
const newProvider = providers[providers.length - 1];
if (newProvider) {
await window.electron.invoke('ai:set-default-provider', newProvider.id);
// Update settings
await window.electron.invoke('storage:update-items', {
'aiProvider': newProvider.id,
'aiModel': model || 'gpt-4o',
'aiTemperature': '0.7',
'aiMaxTokens': '4096',
'aiStreaming': 'true'
});
}
hideLoading();
showSuccess('Provider configured successfully!');
setTimeout(() => goToStep3(), 1500);
} catch (error) {
hideLoading();
showError('Failed to setup provider: ' + error.message);
}
}
function launchAntigravity() {
showLoading('Launching Antigravity...');
// Close this window and launch main app
window.electron.invoke('app:launch-main').then(() => {
window.close();
}).catch(err => {
hideLoading();
showError('Failed to launch Antigravity: ' + err.message);
});
}
function showLoading(message) {
document.getElementById('loading-message').textContent = message;
document.getElementById('loading-overlay').classList.remove('hidden');
}
function hideLoading() {
document.getElementById('loading-overlay').classList.add('hidden');
}
function showError(message) {
alert('Error: ' + message);
}
function showSuccess(message) {
alert('Success: ' + message);
}
// Initialize on load
init();
</script>
</body>
</html>

Binary file not shown.