Skip to content

Commit 93be2e5

Browse files
committed
Update auto-fix mechanism to integrate with new dependency installation system
- Detect missing dependencies (uvicorn/npx) causing startup failures - Trigger permission dialog through new dependency manager - Handle installation results and retry app startup after successful installation - Provide appropriate fallback messages when installation is declined or fails - Integrate seamlessly with enableAutoFixProblems setting and app startup flow - Add dependency manager, checker, and coordinator services
1 parent 407fe10 commit 93be2e5

File tree

6 files changed

+923
-255
lines changed

6 files changed

+923
-255
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React, { useState, useEffect } from "react";
2+
import { DependencyPermissionDialog } from "./DependencyPermissionDialog";
3+
import { InstallationCoordinator } from "@/services/installationCoordinator";
4+
import { showError } from "@/lib/toast";
5+
6+
export function DependencyManager() {
7+
const [isDialogOpen, setIsDialogOpen] = useState(false);
8+
const [missingDependencies, setMissingDependencies] = useState<string[]>([]);
9+
const [isInstalling, setIsInstalling] = useState(false);
10+
const [retryAppId, setRetryAppId] = useState<number | undefined>();
11+
12+
useEffect(() => {
13+
// Listen for dependency dialog requests from main process
14+
const handleShowDialog = (_event: any, data: { missingDependencies: string[], retryAppId?: number }) => {
15+
setMissingDependencies(data.missingDependencies);
16+
setRetryAppId(data.retryAppId);
17+
setIsDialogOpen(true);
18+
};
19+
20+
// Listen for installation progress updates
21+
const handleProgress = (_event: any, progress: any) => {
22+
console.log("Installation progress:", progress);
23+
};
24+
25+
// Listen for installation completion
26+
const handleComplete = (_event: any, result: any) => {
27+
setIsInstalling(false);
28+
if (result.success) {
29+
setIsDialogOpen(false);
30+
// If we have a retryAppId, emit an event to retry app startup
31+
if (result.retryAppId) {
32+
// Emit event to retry app startup
33+
window.electronAPI.invoke("run-app", { appId: result.retryAppId });
34+
}
35+
} else {
36+
showError(`Dependency installation failed: ${result.error}`);
37+
// Show fallback message when installation fails
38+
if (result.retryAppId) {
39+
showError(`App startup failed due to missing dependencies. Please ensure ${result.missingDependencies?.join(", ") || "required dependencies"} are installed manually and try again.`);
40+
}
41+
}
42+
};
43+
44+
// @ts-ignore
45+
window.electronAPI.on("show-dependency-dialog", handleShowDialog);
46+
// @ts-ignore
47+
window.electronAPI.on("dependency-install-progress", handleProgress);
48+
// @ts-ignore
49+
window.electronAPI.on("dependency-install-complete", handleComplete);
50+
51+
return () => {
52+
// @ts-ignore
53+
window.electronAPI.off("show-dependency-dialog", handleShowDialog);
54+
// @ts-ignore
55+
window.electronAPI.off("dependency-install-progress", handleProgress);
56+
// @ts-ignore
57+
window.electronAPI.off("dependency-install-complete", handleComplete);
58+
};
59+
}, []);
60+
61+
const handleInstall = async (onProgress: (progress: any) => void) => {
62+
setIsInstalling(true);
63+
try {
64+
await InstallationCoordinator.installDependencies(missingDependencies, onProgress);
65+
} catch (error) {
66+
throw error;
67+
}
68+
};
69+
70+
const handleSkip = () => {
71+
setIsDialogOpen(false);
72+
setMissingDependencies([]);
73+
setRetryAppId(undefined);
74+
// Show fallback message when user declines installation
75+
if (retryAppId) {
76+
showError(`App startup failed due to missing dependencies. Please install ${missingDependencies.join(", ")} manually and restart the app.`);
77+
}
78+
};
79+
80+
const handleCancel = () => {
81+
setIsDialogOpen(false);
82+
setMissingDependencies([]);
83+
setRetryAppId(undefined);
84+
// Show fallback message when user cancels installation
85+
if (retryAppId) {
86+
showError(`App startup was cancelled. Please install ${missingDependencies.join(", ")} manually to run the app.`);
87+
}
88+
};
89+
90+
return (
91+
<DependencyPermissionDialog
92+
isOpen={isDialogOpen}
93+
missingDependencies={missingDependencies}
94+
onInstall={handleInstall}
95+
onSkip={handleSkip}
96+
onCancel={handleCancel}
97+
/>
98+
);
99+
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import React, { useState } from "react";
2+
import { AlertTriangle, CheckCircle, XCircle, Loader2 } from "lucide-react";
3+
import { Button } from "@/components/ui/button";
4+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
5+
import { LoadingBar } from "@/components/ui/LoadingBar";
6+
import { InstallationProgress } from "@/services/installationCoordinator";
7+
8+
interface DependencyPermissionDialogProps {
9+
isOpen: boolean;
10+
missingDependencies: string[];
11+
onInstall: (onProgress: (progress: InstallationProgress) => void) => Promise<void>;
12+
onSkip: () => void;
13+
onCancel: () => void;
14+
}
15+
16+
export function DependencyPermissionDialog({
17+
isOpen,
18+
missingDependencies,
19+
onInstall,
20+
onSkip,
21+
onCancel,
22+
}: DependencyPermissionDialogProps) {
23+
const [isInstalling, setIsInstalling] = useState(false);
24+
const [installationProgress, setInstallationProgress] = useState<InstallationProgress[]>([]);
25+
const [installationComplete, setInstallationComplete] = useState(false);
26+
27+
if (!isOpen) return null;
28+
29+
const handleInstall = async () => {
30+
setIsInstalling(true);
31+
setInstallationProgress([]);
32+
33+
const progressCallback = (progress: InstallationProgress) => {
34+
setInstallationProgress(prev => {
35+
const existingIndex = prev.findIndex(p => p.dependency === progress.dependency);
36+
if (existingIndex >= 0) {
37+
// Update existing progress
38+
const updated = [...prev];
39+
updated[existingIndex] = progress;
40+
return updated;
41+
} else {
42+
// Add new progress
43+
return [...prev, progress];
44+
}
45+
});
46+
};
47+
48+
try {
49+
await onInstall(progressCallback);
50+
setInstallationComplete(true);
51+
} catch (error) {
52+
console.error("Installation failed:", error);
53+
} finally {
54+
setIsInstalling(false);
55+
}
56+
};
57+
58+
const getProgressIcon = (status: InstallationProgress["status"]) => {
59+
switch (status) {
60+
case "installing":
61+
return <Loader2 className="h-4 w-4 animate-spin text-blue-500" />;
62+
case "success":
63+
return <CheckCircle className="h-4 w-4 text-green-500" />;
64+
case "error":
65+
return <XCircle className="h-4 w-4 text-red-500" />;
66+
default:
67+
return null;
68+
}
69+
};
70+
71+
const getProgressColor = (status: InstallationProgress["status"]) => {
72+
switch (status) {
73+
case "installing":
74+
return "text-blue-600";
75+
case "success":
76+
return "text-green-600";
77+
case "error":
78+
return "text-red-600";
79+
default:
80+
return "text-gray-600";
81+
}
82+
};
83+
84+
const completedCount = installationProgress.filter(p => p.status !== "installing").length;
85+
const totalCount = missingDependencies.length;
86+
const progressPercentage = totalCount > 0 ? (completedCount / totalCount) * 100 : 0;
87+
88+
return (
89+
<div className="fixed inset-0 z-50 overflow-y-auto">
90+
<div className="flex min-h-screen items-center justify-center p-4 text-center sm:p-0">
91+
<div
92+
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
93+
onClick={onCancel}
94+
/>
95+
96+
<div className="relative transform overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
97+
<div className="bg-white dark:bg-gray-800 px-4 pb-4 pt-5 sm:p-6">
98+
<div className="flex items-start">
99+
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-yellow-100 sm:mx-0 sm:h-10 sm:w-10">
100+
<AlertTriangle className="h-6 w-6 text-yellow-600" />
101+
</div>
102+
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left flex-1">
103+
<h3 className="text-lg font-medium leading-6 text-gray-900 dark:text-white">
104+
Missing System Dependencies
105+
</h3>
106+
<div className="mt-2">
107+
<p className="text-sm text-gray-500 dark:text-gray-400">
108+
AliFullStack requires the following dependencies to run backend servers and manage Node.js packages:
109+
</p>
110+
<ul className="mt-2 list-disc list-inside text-sm text-gray-600 dark:text-gray-300">
111+
{missingDependencies.map(dep => (
112+
<li key={dep}>
113+
<strong>{dep}</strong> - {dep === "uvicorn" ? "FastAPI/ASGI server for Python backends" : "Node.js package runner for frontend tools"}
114+
</li>
115+
))}
116+
</ul>
117+
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
118+
Would you like AliFullStack to install these dependencies automatically?
119+
</p>
120+
</div>
121+
</div>
122+
</div>
123+
124+
{/* Installation Progress */}
125+
{isInstalling && (
126+
<div className="mt-6">
127+
<div className="mb-4">
128+
<div className="flex justify-between text-sm text-gray-600 dark:text-gray-400 mb-2">
129+
<span>Installing dependencies...</span>
130+
<span>{completedCount}/{totalCount}</span>
131+
</div>
132+
<LoadingBar isVisible={true} />
133+
</div>
134+
135+
<div className="space-y-2">
136+
{installationProgress.map(progress => (
137+
<div key={progress.dependency} className="flex items-center space-x-2 text-sm">
138+
{getProgressIcon(progress.status)}
139+
<span className="flex-1">{progress.message}</span>
140+
{progress.error && (
141+
<span className="text-xs text-red-500">{progress.error}</span>
142+
)}
143+
</div>
144+
))}
145+
</div>
146+
</div>
147+
)}
148+
149+
{/* Installation Complete */}
150+
{installationComplete && (
151+
<div className="mt-6">
152+
<Card>
153+
<CardHeader className="pb-3">
154+
<CardTitle className="text-sm flex items-center space-x-2">
155+
<CheckCircle className="h-4 w-4 text-green-500" />
156+
<span>Installation Complete</span>
157+
</CardTitle>
158+
</CardHeader>
159+
<CardContent>
160+
<p className="text-sm text-gray-600 dark:text-gray-400">
161+
All dependencies have been installed successfully. Your app will now restart automatically.
162+
</p>
163+
</CardContent>
164+
</Card>
165+
</div>
166+
)}
167+
</div>
168+
169+
<div className="bg-gray-50 dark:bg-gray-700 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
170+
{!installationComplete ? (
171+
<>
172+
<Button
173+
type="button"
174+
className="inline-flex w-full justify-center rounded-md border border-transparent px-4 py-2 text-base font-medium text-white shadow-sm bg-blue-600 hover:bg-blue-700 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
175+
onClick={handleInstall}
176+
disabled={isInstalling}
177+
>
178+
{isInstalling ? (
179+
<>
180+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
181+
Installing...
182+
</>
183+
) : (
184+
"Install Dependencies"
185+
)}
186+
</Button>
187+
<Button
188+
type="button"
189+
variant="outline"
190+
className="mt-3 inline-flex w-full justify-center rounded-md px-4 py-2 text-base font-medium shadow-sm sm:mt-0 sm:w-auto sm:text-sm"
191+
onClick={onSkip}
192+
disabled={isInstalling}
193+
>
194+
Skip for Now
195+
</Button>
196+
</>
197+
) : (
198+
<Button
199+
type="button"
200+
className="inline-flex w-full justify-center rounded-md border border-transparent px-4 py-2 text-base font-medium text-white shadow-sm bg-green-600 hover:bg-green-700 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm"
201+
onClick={onSkip}
202+
>
203+
Continue
204+
</Button>
205+
)}
206+
<Button
207+
type="button"
208+
variant="ghost"
209+
className="mt-3 inline-flex w-full justify-center rounded-md px-4 py-2 text-base font-medium shadow-sm sm:mt-0 sm:w-auto sm:text-sm"
210+
onClick={onCancel}
211+
disabled={isInstalling}
212+
>
213+
Cancel
214+
</Button>
215+
</div>
216+
</div>
217+
</div>
218+
</div>
219+
);
220+
}

0 commit comments

Comments
 (0)