| ... | ... | @@ -308,6 +308,8 @@ import { |
| 308 | 308 | } from "../lib/composerContextRecords"; |
| 309 | 309 | import { type ReviewCommentContext } from "../reviewCommentContext"; |
| 310 | 310 | import { environmentCatalog } from "../connection/catalog"; |
| 311 | import { isDesktopLocalConnectionTarget } from "../connection/desktopLocal"; |
| 312 | import { useEnvironmentDisconnectDelay } from "../hooks/useEnvironmentDisconnectDelay"; |
| 311 | 313 | import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../terminalUiStateStore"; |
| 312 | 314 | import { useKnownTerminalSessions, useThreadRunningTerminalIds } from "../state/terminalSessions"; |
| 313 | 315 | import { useEnvironmentQuery } from "../state/query"; |
| ... | ... | @@ -1497,6 +1499,9 @@ export default function ChatView(props: ChatViewProps) { |
| 1497 | 1499 | const { environments } = useEnvironments(); |
| 1498 | 1500 | const primaryEnvironment = usePrimaryEnvironment(); |
| 1499 | 1501 | const retryEnvironment = useAtomCommand(environmentCatalog.retryNow, { reportFailure: false }); |
| 1502 | const setEnvironmentEnabled = useAtomCommand(environmentCatalog.setEnabled, { |
| 1503 | reportFailure: false, |
| 1504 | }); |
| 1500 | 1505 | const environmentById = useMemo( |
| 1501 | 1506 | () => new Map(environments.map((environment) => [environment.environmentId, environment])), |
| 1502 | 1507 | [environments], |
| ... | ... | @@ -2208,6 +2213,37 @@ export default function ChatView(props: ChatViewProps) { |
| 2208 | 2213 | }, |
| 2209 | 2214 | [retryEnvironment], |
| 2210 | 2215 | ); |
| 2216 | const disconnectDelayElapsed = useEnvironmentDisconnectDelay( |
| 2217 | activeEnvironmentUnavailable ? activeEnvironment.environmentId : null, |
| 2218 | ); |
| 2219 | const canDisconnectActiveEnvironment = |
| 2220 | disconnectDelayElapsed && |
| 2221 | activeEnvironment !== null && |
| 2222 | activeEnvironment.entry.target._tag !== "PrimaryConnectionTarget" && |
| 2223 | !isDesktopLocalConnectionTarget(activeEnvironment.entry.target); |
| 2224 | const [disconnectingEnvironment, setDisconnectingEnvironment] = useState(false); |
| 2225 | const handleDisconnectActiveEnvironment = useCallback( |
| 2226 | async (environmentId: EnvironmentId) => { |
| 2227 | setDisconnectingEnvironment(true); |
| 2228 | const result = await setEnvironmentEnabled({ environmentId, enabled: false }); |
| 2229 | setDisconnectingEnvironment(false); |
| 2230 | if (result._tag === "Failure") { |
| 2231 | if (!isAtomCommandInterrupted(result)) { |
| 2232 | const error = squashAtomCommandFailure(result); |
| 2233 | toastManager.add( |
| 2234 | stackedThreadToast({ |
| 2235 | type: "error", |
| 2236 | title: "Could not disconnect server", |
| 2237 | description: error instanceof Error ? error.message : "Failed to disconnect.", |
| 2238 | }), |
| 2239 | ); |
| 2240 | } |
| 2241 | return; |
| 2242 | } |
| 2243 | void navigate({ to: "/", replace: true }); |
| 2244 | }, |
| 2245 | [navigate, setEnvironmentEnabled], |
| 2246 | ); |
| 2211 | 2247 | const logicalProjectEnvironments = useMemo(() => { |
| 2212 | 2248 | if (!activeProject) return []; |
| 2213 | 2249 | const logicalKey = deriveLogicalProjectKeyFromSettings(activeProject, projectGroupingSettings); |
| ... | ... | @@ -2448,6 +2484,20 @@ export default function ChatView(props: ChatViewProps) { |
| 2448 | 2484 | const items: ComposerBannerStackItem[] = []; |
| 2449 | 2485 | const updateRunning = serverUpdateState.status === "running"; |
| 2450 | 2486 | const unavailableConnection = activeEnvironmentUnavailableState?.connection ?? null; |
| 2487 | const disconnectAction = |
| 2488 | canDisconnectActiveEnvironment && activeEnvironmentUnavailableState ? ( |
| 2489 | <Button |
| 2490 | size="xs" |
| 2491 | variant="ghost" |
| 2492 | disabled={disconnectingEnvironment} |
| 2493 | title="Hide this server's threads. Switch it on again in Connections." |
| 2494 | onClick={() => |
| 2495 | void handleDisconnectActiveEnvironment(activeEnvironmentUnavailableState.environmentId) |
| 2496 | } |
| 2497 | > |
| 2498 | Disconnect server |
| 2499 | </Button> |
| 2500 | ) : undefined; |
| 2451 | 2501 | const environmentReconnecting = |
| 2452 | 2502 | unavailableConnection !== null && |
| 2453 | 2503 | (unavailableConnection.phase === "connecting" || |
| ... | ... | @@ -2481,6 +2531,7 @@ export default function ChatView(props: ChatViewProps) { |
| 2481 | 2531 | ), |
| 2482 | 2532 | title: `${unavailableConnection.phase === "connecting" ? "Connecting" : "Reconnecting"} to ${activeEnvironmentUnavailableState.label}`, |
| 2483 | 2533 | description: "Finishing an update", |
| 2534 | actions: disconnectAction, |
| 2484 | 2535 | }); |
| 2485 | 2536 | } else { |
| 2486 | 2537 | items.push({ |
| ... | ... | @@ -2488,28 +2539,22 @@ export default function ChatView(props: ChatViewProps) { |
| 2488 | 2539 | variant: unavailableConnection.phase === "error" ? "error" : "warning", |
| 2489 | 2540 | icon: <WifiOffIcon />, |
| 2490 | 2541 | title: `${activeEnvironmentUnavailableState.label} is ${environmentReconnecting ? "reconnecting" : "offline"}`, |
| 2491 | | description: environmentReconnecting ? "Trying again" : "Reconnect to continue", |
| 2492 | 2542 | actions: ( |
| 2493 | 2543 | <> |
| 2494 | | <Button |
| 2495 | | size="xs" |
| 2496 | | variant="ghost" |
| 2497 | | disabled={environmentReconnecting} |
| 2498 | | onClick={() => |
| 2499 | | void handleReconnectActiveEnvironment( |
| 2500 | | activeEnvironmentUnavailableState.environmentId, |
| 2501 | | ) |
| 2502 | | } |
| 2503 | | > |
| 2504 | | {environmentReconnecting ? "Reconnecting..." : "Reconnect"} |
| 2505 | | </Button> |
| 2506 | | <Button |
| 2507 | | size="xs" |
| 2508 | | variant="ghost" |
| 2509 | | onClick={() => void navigate({ to: "/settings/connections" })} |
| 2510 | | > |
| 2511 | | Connections |
| 2512 | | </Button> |
| 2544 | {!environmentReconnecting ? ( |
| 2545 | <Button |
| 2546 | size="xs" |
| 2547 | variant="ghost" |
| 2548 | onClick={() => |
| 2549 | void handleReconnectActiveEnvironment( |
| 2550 | activeEnvironmentUnavailableState.environmentId, |
| 2551 | ) |
| 2552 | } |
| 2553 | > |
| 2554 | Reconnect |
| 2555 | </Button> |
| 2556 | ) : null} |
| 2557 | {disconnectAction} |
| 2513 | 2558 | </> |
| 2514 | 2559 | ), |
| 2515 | 2560 | }); |
| ... | ... | @@ -2564,22 +2609,22 @@ export default function ChatView(props: ChatViewProps) { |
| 2564 | 2609 | (versionMismatchSelfUpdate !== "desktop-managed" || !versionMismatchDesktopAppUpdate) |
| 2565 | 2610 | ? serverUpdateGuidance(versionMismatchSelfUpdate) |
| 2566 | 2611 | : undefined, |
| 2567 | | actions: |
| 2568 | | updateInProgress || |
| 2569 | | !versionMismatch || |
| 2612 | actions: updateInProgress ? ( |
| 2613 | disconnectAction |
| 2614 | ) : !versionMismatch || |
| 2570 | 2615 | (versionMismatchSelfUpdate === "desktop-managed" && |
| 2571 | 2616 | !versionMismatchDesktopAppUpdate) ? undefined : ( |
| 2572 | | <ServerUpdateAction |
| 2573 | | environmentId={serverUpdateEnvironmentId} |
| 2574 | | serverLabel={versionMismatchServerLabel} |
| 2575 | | selfUpdate={versionMismatchSelfUpdate} |
| 2576 | | desktopAppUpdate={versionMismatchDesktopAppUpdate} |
| 2577 | | threadContinuation={versionMismatchThreadContinuation} |
| 2578 | | targetVersion={versionMismatch.clientVersion} |
| 2579 | | label={updateFailed ? "Retry" : "Update"} |
| 2580 | | variant="ghost" |
| 2581 | | /> |
| 2582 | | ), |
| 2617 | <ServerUpdateAction |
| 2618 | environmentId={serverUpdateEnvironmentId} |
| 2619 | serverLabel={versionMismatchServerLabel} |
| 2620 | selfUpdate={versionMismatchSelfUpdate} |
| 2621 | desktopAppUpdate={versionMismatchDesktopAppUpdate} |
| 2622 | threadContinuation={versionMismatchThreadContinuation} |
| 2623 | targetVersion={versionMismatch.clientVersion} |
| 2624 | label={updateFailed ? "Retry" : "Update"} |
| 2625 | variant="ghost" |
| 2626 | /> |
| 2627 | ), |
| 2583 | 2628 | ...(updateInProgress || (!updateFailed && !versionMismatchDismissKey) |
| 2584 | 2629 | ? {} |
| 2585 | 2630 | : { |
| ... | ... | @@ -2603,7 +2648,9 @@ export default function ChatView(props: ChatViewProps) { |
| 2603 | 2648 | activeEnvironmentUnavailableState, |
| 2604 | 2649 | reconnectWarningGraceElapsed, |
| 2605 | 2650 | handleReconnectActiveEnvironment, |
| 2606 | | navigate, |
| 2651 | canDisconnectActiveEnvironment, |
| 2652 | disconnectingEnvironment, |
| 2653 | handleDisconnectActiveEnvironment, |
| 2607 | 2654 | setDismissedVersionMismatchKey, |
| 2608 | 2655 | showVersionMismatchBanner, |
| 2609 | 2656 | serverUpdateFailureDismissed, |