Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src-dotnet/PuduLauncher/Services/TtsInstallService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,25 @@ public async Task RunInstallerAsync(string extractDir, string installPath, Cance
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
// Redirect stdin so the installer (and the bundled Python it spawns for
// the python env step) gets a private handle instead of inheriting the
// sidecar's stdin, a pipe the Rust host owns for the ACK/SHUTDOWN signal.
// The bundled Python blocks while initializing its standard streams on
// that inherited pipe, hanging the install. See TtsServerService for the
// same fix on the server process.
RedirectStandardInput = true,
};

using var process = new Process();
process.StartInfo = psi;
process.EnableRaisingEvents = true;
process.Start();

// The installer never reads stdin; close it so the child sees EOF rather
// than an open idle pipe. The redirect itself is what keeps it off the
// sidecar's inherited stdin (see ProcessStartInfo above).
process.StandardInput.Close();

lock (_processLock)
{
_currentInstallerProcess = process;
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "pudu-launcher",
"version": "2.0.1",
"version": "2.0.2",
"identifier": "com.corp0.pudu-launcher",
"build": {
"beforeDevCommand": "npm run generate-ts && npm run build-sidecar && npm run dev",
Expand Down
1 change: 1 addition & 0 deletions src/components/layouts/tts/TtsInstallerLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default function TtsInstallerLayout(props: TtsInstallerLayoutProps) {
currentStep={currentStep}
stepLabels={stepLabels}
isComplete={isComplete}
startNumber={0}
/>

{(statusMessage || statusLabel) && (
Expand Down
8 changes: 6 additions & 2 deletions src/components/organisms/common/PuduStepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ interface PuduStepperProps {
currentStep: number;
stepLabels?: string[];
isComplete?: boolean;
// Number shown on the first indicator. Defaults to 1. Set to 0 to align the
// displayed numbers with externally numbered phases (for example installer
// log lines that count from a zero-based "prepare" step).
startNumber?: number;
}

export default function PuduStepper(props: PuduStepperProps) {
const { maxSteps, currentStep, stepLabels, isComplete = false } = props;
const { maxSteps, currentStep, stepLabels, isComplete = false, startNumber = 1 } = props;
const stepCount = Math.max(1, Math.floor(maxSteps));
const activeStep = Math.min(Math.max(Math.floor(currentStep), 1), stepCount);

Expand All @@ -32,7 +36,7 @@ export default function PuduStepper(props: PuduStepperProps) {
variant={indicatorVariant}
color={indicatorColor}
>
{isCompleted ? <CheckRounded fontSize="inherit" /> : stepNumber}
{isCompleted ? <CheckRounded fontSize="inherit" /> : stepNumber - 1 + startNumber}
</StepIndicator>
}
>
Expand Down
33 changes: 18 additions & 15 deletions src/contextProviders/TtsInstallerContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,8 @@ export function TtsInstallerContextProvider(props: PropsWithChildren) {
const { ttsState, status, statusMessage, installLogs, clearInstallLogs } = useTtsState();

const [isInstallerOpen, setIsInstallerOpen] = useState(false);
const [maxReachedStep, setMaxReachedStep] = useState(1);
const installSessionRef = useRef(false);
const prevStatusRef = useRef<number | null>(null);
const prevLogLengthRef = useRef(0);
const prevUpdateAvailableRef = useRef(false);

const beginInstallSession = () => {
Expand All @@ -98,7 +96,6 @@ export function TtsInstallerContextProvider(props: PropsWithChildren) {

installSessionRef.current = true;
clearInstallLogs();
setMaxReachedStep(1);
};

// React to status changes
Expand Down Expand Up @@ -132,25 +129,18 @@ export function TtsInstallerContextProvider(props: PropsWithChildren) {
showInfo({ message: "TTS server stopped" });
}

setMaxReachedStep((prev) => Math.max(prev, stepFromStatus(status)));
prevStatusRef.current = status;
}, [status]);

// React to new install log lines
// React to new install log lines. This effect only opens the installer modal
// when logs start streaming. The active step is derived from installLogs
// directly (see currentStep below) rather than tracked incrementally, so it
// can never desync from the log contents.
useEffect(() => {
if (installLogs.length > 0 && !installSessionRef.current) {
beginInstallSession();
setIsInstallerOpen(true);
}

for (let i = prevLogLengthRef.current; i < installLogs.length; i++) {
const parsed = inferStepFromLogLine(installLogs[i]);
if (parsed !== null) {
setMaxReachedStep((prev) => Math.max(prev, parsed));
}
}

prevLogLengthRef.current = installLogs.length;
}, [installLogs]);

useEffect(() => {
Expand Down Expand Up @@ -184,7 +174,20 @@ export function TtsInstallerContextProvider(props: PropsWithChildren) {
const statusLabel = status !== null
? (TTS_STATUS_LABELS[status] ?? `Status ${status}`)
: "Unknown";
const currentStep = maxReachedStep;
// Derive the active step from the log contents on every render instead of
// tracking it incrementally. An incremental index cursor desynced from
// installLogs whenever the shared log array was cleared for a new session,
// skipping step markers and freezing the stepper (for example showing
// "Python" while packages were already installing). Re-scanning is cheap
// (logs are capped at 400 lines) and cannot drift from what the log shows.
const stepFromLogs = installLogs.reduce((maxStep, line) => {
const parsed = inferStepFromLogLine(line);
return parsed === null ? maxStep : Math.max(maxStep, parsed);
}, 1);
const currentStep = Math.min(
Math.max(stepFromLogs, stepFromStatus(status)),
INSTALL_STEP_LABELS.length,
);
const isInstallComplete = status === TTS_STATUS.Installed;
const stepStatusMessage = isInstallComplete
? INSTALL_SUCCESS_MESSAGE
Expand Down
Loading