import { type ApprovalRequestId, DEFAULT_MODEL, defaultInstanceIdForDriver, type EnvironmentId, type MessageId, type ModelSelection, type ProjectScript, type ProjectId, type ProviderApprovalDecision, ProviderInstanceId, type ServerProvider, type ResolvedKeybindingsConfig, type ScopedThreadRef, type ThreadId, type TurnId, type KeybindingCommand, OrchestrationThreadActivity, ProviderInteractionMode, ProviderDriverKind, RuntimeMode, TerminalOpenInput,} from "@t3tools/contracts";import { connectionStatusTitle, type EnvironmentConnectionPresentation,} from "@t3tools/client-runtime/connection";import { effectiveSettled, effectiveSnoozed } from "@t3tools/client-runtime/state/thread-settled";import { parseScopedThreadKey, scopedThreadKey, scopeProjectRef, scopeThreadRef,} from "@t3tools/client-runtime/environment";import { applyClaudePromptEffortPrefix, createModelSelection, resolvePromptInjectedEffort,} from "@t3tools/shared/model";import { CHAT_LIST_ANCHOR_OFFSET } from "@t3tools/shared/chatList";import { projectScriptCwd, projectScriptRuntimeEnv } from "@t3tools/shared/projectScripts";import { truncate } from "@t3tools/shared/String";import { nextTerminalId, resolveTerminalSessionLabel } from "@t3tools/shared/terminalLabels";import { Debouncer } from "@tanstack/react-pacer";import { useAtomValue } from "@effect/atom-react";import { lazy, memo, Suspense, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState,} from "react";import { flushSync } from "react-dom";import { useNavigate } from "@tanstack/react-router";import { useShallow } from "zustand/react/shallow";import { isAtomCommandInterrupted, mapAtomCommandResult, settlePromise, squashAtomCommandFailure, type AtomCommandResult,} from "@t3tools/client-runtime/state/runtime";import * as Cause from "effect/Cause";import { AsyncResult } from "effect/unstable/reactivity";import { isElectron } from "../env";import { readLocalApi } from "../localApi";import { useDiffPanelStore } from "../diffPanelStore";import { collapseExpandedComposerCursor, parseStandaloneComposerSlashCommand,} from "../composer-logic";import { derivePendingApprovals, derivePendingUserInputs, derivePhase, deriveTimelineEntries, deriveActiveWorkStartedAt, deriveActivePlanState, findSidebarProposedPlan, findLatestProposedPlan, deriveWorkLogEntries, hasActionableProposedPlan, isLatestTurnSettled,} from "../session-logic";import { type LegendListRef } from "@legendapp/list/react";import { getAnchoredTurnMetrics, type TimelineScrollMode } from "./chat/timelineScrollAnchoring";import { buildPendingUserInputAnswers, derivePendingUserInputProgress, setPendingUserInputCustomAnswer, togglePendingUserInputOptionSelection, type PendingUserInputDraftAnswer,} from "../pendingUserInput";import { useUiStateStore } from "../uiStateStore";import { buildPlanImplementationThreadTitle, buildPlanImplementationPrompt, resolvePlanFollowUpSubmission,} from "../proposedPlan";import { DEFAULT_INTERACTION_MODE, DEFAULT_RUNTIME_MODE, DEFAULT_THREAD_TERMINAL_ID, MAX_TERMINALS_PER_GROUP, type ChatMessage, type SessionPhase, type Thread, type TurnDiffSummary,} from "../types";import { useTheme } from "../hooks/useTheme";import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries";import { isCommandPaletteOpen } from "../commandPaletteBus";import { buildTemporaryWorktreeBranchName } from "@t3tools/shared/git";import { useMediaQuery } from "../hooks/useMediaQuery";import { RIGHT_PANEL_INLINE_LAYOUT_MEDIA_QUERY } from "../rightPanelLayout";import { selectActiveRightPanel, selectActiveRightPanelSurface, selectThreadRightPanelState, type RightPanelSurface, useRightPanelStore,} from "../rightPanelStore";import { isPreviewSupportedInRuntime, setActivePreviewTab, useThreadPreviewState,} from "../previewStateStore";import { addBrowserSurface } from "./preview/addBrowserSurface";import { closePreviewSession } from "./preview/closePreviewSession";import { subscribePreviewAction } from "./preview/previewActionBus";import { getConfiguredPreviewUrls } from "./preview/previewEmptyStateLogic";import { RightPanelTabs } from "./RightPanelTabs";import { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider";import { BranchToolbar } from "./BranchToolbar";import { resolveShortcutCommand, shortcutLabelForCommand } from "../keybindings";import PlanSidebar from "./PlanSidebar";import ThreadTerminalDrawer from "./ThreadTerminalDrawer";import { AlarmClockIcon, CheckCircle2Icon, ChevronDownIcon, GitBranchIcon, TriangleAlertIcon, WifiOffIcon,} from "lucide-react";import { cn, randomHex } from "~/lib/utils";import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar";import { stackedThreadToast, toastManager } from "./ui/toast";import { decodeProjectScriptKeybindingRule } from "~/lib/projectScriptKeybindings";import { type NewProjectScriptInput } from "./ProjectScriptsControl";import { buildProjectScript, commandForProjectScript, nextProjectScriptId, projectScriptIdFromCommand,} from "~/projectScripts";import { newDraftId, newMessageId, newThreadId } from "~/lib/utils";import { getProviderModelCapabilities, resolveSelectableProvider } from "../providerModels";import { NO_PROVIDER_MODEL_SELECTION } from "../providerInstances";import { useClientSettings, useEnvironmentSettings } from "../hooks/useSettings";import { useNowMinute } from "../hooks/useNowMinute";import { resolveAppModelSelectionForInstance } from "../modelSelection";import { getTerminalFocusOwner } from "../lib/terminalFocus";import { resolveNewDraftStartFromOrigin } from "../lib/chatThreadActions";import { deriveLogicalProjectKeyFromSettings, selectProjectGroupingSettings,} from "../logicalProject";import { buildDraftThreadRouteParams } from "../threadRoutes";import { type ComposerImageAttachment, type DraftThreadEnvMode, useComposerDraftStore, type DraftId,} from "../composerDraftStore";import { appendTerminalContextsToPrompt, formatTerminalContextLabel, type TerminalContextDraft, type TerminalContextSelection,} from "../lib/terminalContext";import { appendElementContextsToPrompt, type ElementContextDraft, formatElementContextLabel,} from "../lib/elementContext";import { appendPreviewAnnotationPrompt } from "../lib/previewAnnotation";import { appendReviewCommentsToPrompt, type ReviewCommentContext } from "../reviewCommentContext";import { environmentCatalog } from "../connection/catalog";import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../terminalUiStateStore";import { useKnownTerminalSessions, useThreadRunningTerminalIds } from "../state/terminalSessions";import { projectEnvironment } from "../state/projects";import { useEnvironmentQuery } from "../state/query";import { primaryServerAvailableEditorsAtom, primaryServerKeybindingsAtom, primaryServerSettingsAtom, serverEnvironment,} from "../state/server";import { terminalEnvironment } from "../state/terminal";import { threadEnvironment } from "../state/threads";import { vcsEnvironment } from "../state/vcs";import { useEnvironments, usePrimaryEnvironment } from "../state/environments";import { useProject, useProjects, useThread, useThreadProposedPlans, useThreadRefs, useThreadShell,} from "../state/entities";import { environmentShell } from "../state/shell";import { ChatComposer, type ChatComposerHandle } from "./chat/ChatComposer";import { DraftHeroHeadline } from "./chat/DraftHeroHeadline";import { ExpandedImageDialog } from "./chat/ExpandedImageDialog";import { PullRequestThreadDialog } from "./PullRequestThreadDialog";import { MessagesTimeline } from "./chat/MessagesTimeline";import { ChatHeader } from "./chat/ChatHeader";import { PanelLayoutControls, RightPanelMaximizeControl } from "./chat/PanelLayoutControls";import { type ExpandedImagePreview } from "./chat/ExpandedImagePreview";import { NoActiveThreadState } from "./NoActiveThreadState";import { resolveEffectiveEnvMode, resolveLocalCheckoutBranchMismatch } from "./BranchToolbar.logic";import { getProviderStatusBannerKey, ProviderStatusBanner, shouldShowProviderStatusBanner,} from "./chat/ProviderStatusBanner";import { ThreadErrorBanner } from "./chat/ThreadErrorBanner";import { resolveThreadPr } from "./ThreadStatusIndicators";import { ComposerBannerStack, type ComposerBannerStackItem } from "./chat/ComposerBannerStack";import { DRAFT_HERO_TRANSITION_ANIMATION_ID, DRAFT_HERO_TRANSITION_DURATION_MS, DRAFT_HERO_TRANSITION_EASING, MOBILE_COMPOSER_VIEW_TRANSITION_NAME, MOBILE_DRAFT_HEADLINE_VIEW_TRANSITION_NAME, runMobileComposerTransition,} from "./chat/draftHeroTransition";import { MAX_HIDDEN_MOUNTED_TERMINAL_THREADS, branchMismatchKey, buildExpiredTerminalContextToastCopy, buildLocalDraftThread, buildThreadTurnInterruptInput, collectUserMessageBlobPreviewUrls, createLocalDispatchSnapshot, deriveComposerSendState, dismissBranchMismatchForSession, hasServerAcknowledgedLocalDispatch, isBranchMismatchDismissedForSession, shouldShowBranchMismatchBanner, getStartedThreadModelChangeBlockReason, LAST_INVOKED_SCRIPT_BY_PROJECT_KEY, LastInvokedScriptByProjectSchema, type LocalDispatchSnapshot, PullRequestDialogState, cloneComposerImageForRetry, deriveLockedProvider, readFileAsDataUrl, reconcileMountedTerminalThreadIds, resolveThreadMetadataUpdateForNextTurn, resolveSendEnvMode, revokeBlobPreviewUrl, revokeUserMessagePreviewUrls, waitForStartedServerThread,} from "./ChatView.logic";import { useLocalStorage } from "~/hooks/useLocalStorage";import { useComposerHandleContext } from "../composerHandleContext";import { sanitizeThreadErrorMessage } from "~/rpc/transportError";import { RightPanelSheet } from "./RightPanelSheet";import { previewEnvironment } from "../state/preview";import { useAtomCommand } from "../state/use-atom-command";import { Button } from "./ui/button";import { AlertDialog, AlertDialogClose, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogPopup, AlertDialogTitle,} from "./ui/alert-dialog";import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";import { ServerUpdateAction } from "./ServerUpdateAction";import { buildVersionMismatchDismissalKey, dismissVersionMismatch, isVersionMismatchDismissed, resolveServerConfigVersionMismatch, resolveServerSelfUpdateCapability, serverUpdateGuidance,} from "../versionSkew";import { useAssetUrls } from "../assets/assetUrls"; const IMAGE_ONLY_BOOTSTRAP_PROMPT = "[User attached one or more images without additional text. Respond using the conversation context and the attached image(s).]";const EMPTY_ACTIVITIES: OrchestrationThreadActivity[] = [];const EMPTY_PROVIDERS: ServerProvider[] = [];const EMPTY_PROVIDER_SKILLS: ServerProvider["skills"] = [];const EMPTY_PENDING_USER_INPUT_ANSWERS: Record<string, PendingUserInputDraftAnswer> = {};function useDraftHeroLayoutTransition(isDraftHeroState: boolean) { const transitionGroupRef = useRef<HTMLDivElement | null>(null); const composerAnchorRef = useRef<HTMLDivElement | null>(null); const previousStateRef = useRef(isDraftHeroState); const previousComposerRectRef = useRef<DOMRect | null>(null); const animationRef = useRef<Animation | null>(null); const attachTransitionGroupRef = (element: HTMLDivElement | null) => { transitionGroupRef.current = element; }; const attachComposerAnchorRef = (element: HTMLDivElement | null) => { composerAnchorRef.current = element; }; const captureComposerRect = () => { previousComposerRectRef.current = composerAnchorRef.current?.getBoundingClientRect() ?? null; }; useLayoutEffect(() => { const transitionGroup = transitionGroupRef.current; const nextComposerRect = composerAnchorRef.current?.getBoundingClientRect() ?? null; const stateChanged = previousStateRef.current !== isDraftHeroState; const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches; const mobileComposerTransitionActive = typeof document !== "undefined" && document.documentElement.dataset.mobileComposerRouteTransition === "true"; animationRef.current?.cancel(); animationRef.current = null; const previousComposerRect = previousComposerRectRef.current; if ( stateChanged && !prefersReducedMotion && !mobileComposerTransitionActive && transitionGroup && previousComposerRect && nextComposerRect && typeof transitionGroup.animate === "function" ) { const translateX = previousComposerRect.left - nextComposerRect.left; const translateY = previousComposerRect.top - nextComposerRect.top; if (Math.abs(translateX) >= 0.5 || Math.abs(translateY) >= 0.5) { const animation = transitionGroup.animate( [ { transform: `translate3d(${translateX}px, ${translateY}px, 0)` }, { transform: "translate3d(0, 0, 0)" }, ], { duration: DRAFT_HERO_TRANSITION_DURATION_MS, easing: DRAFT_HERO_TRANSITION_EASING, }, ); animation.id = DRAFT_HERO_TRANSITION_ANIMATION_ID; animationRef.current = animation; void animation.finished .catch(() => undefined) .then(() => { if (animationRef.current !== animation) { return; } animationRef.current = null; }); } } previousStateRef.current = isDraftHeroState; previousComposerRectRef.current = nextComposerRect; }, [isDraftHeroState]); return [attachTransitionGroupRef, attachComposerAnchorRef, captureComposerRect] as const;}const PreviewPanel = lazy(() => import("./preview/PreviewPanel").then((module) => ({ default: module.PreviewPanel })),);const DiffPanel = lazy(() => import("./DiffPanel"));const FilePreviewPanel = lazy(() => import("./files/FilePreviewPanel"));const EMPTY_PENDING_FILE_SURFACE_IDS: ReadonlySet<string> = new Set();const TYPE_TO_FOCUS_EDITABLE_SELECTOR = [ "input", "textarea", "select", '[contenteditable="true"]', '[contenteditable="plaintext-only"]', '[role="textbox"]',].join(",");const TYPE_TO_FOCUS_INTERACTIVE_SELECTOR = [ "button", "a[href]", "summary", '[role="button"]', '[role="checkbox"]', '[role="menuitem"]', '[role="option"]', '[role="radio"]', '[role="switch"]', '[role="tab"]',].join(",");const TYPE_TO_FOCUS_FLOATING_LAYER_SELECTOR = [ '[data-slot="dialog"]', '[data-slot="menu-popup"]', '[data-slot="select-popup"]', '[data-slot="popover-popup"]', '[data-slot="combobox-popup"]', '[data-slot="autocomplete-popup"]',].join(","); type EnvironmentUnavailableState = { readonly environmentId: EnvironmentId; readonly label: string; readonly connection: EnvironmentConnectionPresentation;}; type ThreadPlanCatalogEntry = Pick<Thread, "id" | "proposedPlans">; function eventPathContainsSelector(event: Event, selector: string): boolean { const path = event.composedPath(); if (path.length === 0 && event.target) { path.push(event.target); } return path.some((target) => target instanceof Element && target.closest(selector));} function shouldTypeToFocusComposer(event: KeyboardEvent): boolean { if (event.defaultPrevented || event.isComposing) return false; if (event.metaKey || event.ctrlKey || event.altKey) return false; if (event.key.length !== 1) return false; if (eventPathContainsSelector(event, TYPE_TO_FOCUS_EDITABLE_SELECTOR)) return false; if (eventPathContainsSelector(event, TYPE_TO_FOCUS_INTERACTIVE_SELECTOR)) return false; if (document.querySelector(TYPE_TO_FOCUS_FLOATING_LAYER_SELECTOR)) return false; return true;} function formatOutgoingPrompt(params: { provider: ProviderDriverKind; model: string | null; models: ReadonlyArray<ServerProvider["models"][number]>; effort: string | null; text: string;}): string { const caps = getProviderModelCapabilities(params.models, params.model, params.provider); const promptEffort = resolvePromptInjectedEffort(caps, params.effort); return applyClaudePromptEffortPrefix(params.text, promptEffort);}const SCRIPT_TERMINAL_COLS = 120;const SCRIPT_TERMINAL_ROWS = 30; type ChatViewProps = | { environmentId: EnvironmentId; threadId: ThreadId; onDiffPanelOpen?: () => void; reserveTitleBarControlInset?: boolean; forceExpandedMobileComposer?: boolean; routeKind: "server"; draftId?: never; } | { environmentId: EnvironmentId; threadId: ThreadId; onDiffPanelOpen?: () => void; reserveTitleBarControlInset?: boolean; forceExpandedMobileComposer?: boolean; routeKind: "draft"; draftId: DraftId; }; interface TerminalLaunchContext { threadId: ThreadId; cwd: string; worktreePath: string | null;} type PersistentTerminalLaunchContext = Pick<TerminalLaunchContext, "cwd" | "worktreePath">; function useLocalDispatchState(input: { activeThread: Thread | undefined; activeLatestTurn: Thread["latestTurn"] | null; phase: SessionPhase; activePendingApproval: ApprovalRequestId | null; activePendingUserInput: ApprovalRequestId | null; threadError: string | null | undefined;}) { const [localDispatch, setLocalDispatch] = useState<LocalDispatchSnapshot | null>(null); const latestUserMessageId = input.activeThread?.messages.findLast((message) => message.role === "user")?.id ?? null; const resetLocalDispatch = useCallback(() => { setLocalDispatch(null); }, []); const serverAcknowledgedLocalDispatch = useMemo( () => hasServerAcknowledgedLocalDispatch({Lines 1–500 of 6053. Load more
import { type ApprovalRequestId, DEFAULT_MODEL, defaultInstanceIdForDriver, type EnvironmentId, type MessageId, type ModelSelection, type ProjectScript, type ProjectId, type ProviderApprovalDecision, ProviderInstanceId, type ServerProvider, type ResolvedKeybindingsConfig, type ScopedThreadRef, type ThreadId, type TurnId, type KeybindingCommand, OrchestrationThreadActivity, ProviderInteractionMode, ProviderDriverKind, RuntimeMode, TerminalOpenInput, } from "@t3tools/contracts"; import { connectionStatusTitle, type EnvironmentConnectionPresentation, } from "@t3tools/client-runtime/connection"; import { effectiveSettled, effectiveSnoozed } from "@t3tools/client-runtime/state/thread-settled"; import { parseScopedThreadKey, scopedThreadKey, scopeProjectRef, scopeThreadRef, } from "@t3tools/client-runtime/environment"; import { applyClaudePromptEffortPrefix, createModelSelection, resolvePromptInjectedEffort, } from "@t3tools/shared/model"; import { CHAT_LIST_ANCHOR_OFFSET } from "@t3tools/shared/chatList"; import { projectScriptCwd, projectScriptRuntimeEnv } from "@t3tools/shared/projectScripts"; import { truncate } from "@t3tools/shared/String"; import { nextTerminalId, resolveTerminalSessionLabel } from "@t3tools/shared/terminalLabels"; import { Debouncer } from "@tanstack/react-pacer"; import { useAtomValue } from "@effect/atom-react"; import { lazy, memo, Suspense, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react"; import { flushSync } from "react-dom"; import { useNavigate } from "@tanstack/react-router"; import { useShallow } from "zustand/react/shallow"; import { isAtomCommandInterrupted, mapAtomCommandResult, settlePromise, squashAtomCommandFailure, type AtomCommandResult, } from "@t3tools/client-runtime/state/runtime"; import * as Cause from "effect/Cause"; import { AsyncResult } from "effect/unstable/reactivity"; import { isElectron } from "../env"; import { readLocalApi } from "../localApi"; import { useDiffPanelStore } from "../diffPanelStore"; import { collapseExpandedComposerCursor, parseStandaloneComposerSlashCommand, } from "../composer-logic"; import { derivePendingApprovals, derivePendingUserInputs, derivePhase, deriveTimelineEntries, deriveActiveWorkStartedAt, deriveActivePlanState, findSidebarProposedPlan, findLatestProposedPlan, deriveWorkLogEntries, hasActionableProposedPlan, isLatestTurnSettled, } from "../session-logic"; import { type LegendListRef } from "@legendapp/list/react"; import { getAnchoredTurnMetrics, type TimelineScrollMode } from "./chat/timelineScrollAnchoring"; import { buildPendingUserInputAnswers, derivePendingUserInputProgress, setPendingUserInputCustomAnswer, togglePendingUserInputOptionSelection, type PendingUserInputDraftAnswer, } from "../pendingUserInput"; import { useUiStateStore } from "../uiStateStore"; import { buildPlanImplementationThreadTitle, buildPlanImplementationPrompt, resolvePlanFollowUpSubmission, } from "../proposedPlan"; import { DEFAULT_INTERACTION_MODE, DEFAULT_RUNTIME_MODE, DEFAULT_THREAD_TERMINAL_ID, MAX_TERMINALS_PER_GROUP, type ChatMessage, type SessionPhase, type Thread, type TurnDiffSummary, } from "../types"; import { useTheme } from "../hooks/useTheme"; import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries"; import { isCommandPaletteOpen } from "../commandPaletteBus"; import { buildTemporaryWorktreeBranchName } from "@t3tools/shared/git"; import { useMediaQuery } from "../hooks/useMediaQuery"; import { RIGHT_PANEL_INLINE_LAYOUT_MEDIA_QUERY } from "../rightPanelLayout"; import { selectActiveRightPanel, selectActiveRightPanelSurface, selectThreadRightPanelState, type RightPanelSurface, useRightPanelStore, } from "../rightPanelStore"; import { isPreviewSupportedInRuntime, setActivePreviewTab, useThreadPreviewState, } from "../previewStateStore"; import { addBrowserSurface } from "./preview/addBrowserSurface"; import { closePreviewSession } from "./preview/closePreviewSession"; import { subscribePreviewAction } from "./preview/previewActionBus"; import { getConfiguredPreviewUrls } from "./preview/previewEmptyStateLogic"; import { RightPanelTabs } from "./RightPanelTabs"; import { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider"; import { BranchToolbar } from "./BranchToolbar"; import { resolveShortcutCommand, shortcutLabelForCommand } from "../keybindings"; import PlanSidebar from "./PlanSidebar"; import ThreadTerminalDrawer from "./ThreadTerminalDrawer"; import { AlarmClockIcon, CheckCircle2Icon, ChevronDownIcon, GitBranchIcon, TriangleAlertIcon, WifiOffIcon, } from "lucide-react"; import { cn, randomHex } from "~/lib/utils"; import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar"; import { stackedThreadToast, toastManager } from "./ui/toast"; import { decodeProjectScriptKeybindingRule } from "~/lib/projectScriptKeybindings"; import { type NewProjectScriptInput } from "./ProjectScriptsControl"; import { buildProjectScript, commandForProjectScript, nextProjectScriptId, projectScriptIdFromCommand, } from "~/projectScripts"; import { newDraftId, newMessageId, newThreadId } from "~/lib/utils"; import { getProviderModelCapabilities, resolveSelectableProvider } from "../providerModels"; import { NO_PROVIDER_MODEL_SELECTION } from "../providerInstances"; import { useClientSettings, useEnvironmentSettings } from "../hooks/useSettings"; import { useNowMinute } from "../hooks/useNowMinute"; import { resolveAppModelSelectionForInstance } from "../modelSelection"; import { getTerminalFocusOwner } from "../lib/terminalFocus"; import { resolveNewDraftStartFromOrigin } from "../lib/chatThreadActions"; import { deriveLogicalProjectKeyFromSettings, selectProjectGroupingSettings, } from "../logicalProject"; import { buildDraftThreadRouteParams } from "../threadRoutes"; import { type ComposerImageAttachment, type DraftThreadEnvMode, useComposerDraftStore, type DraftId, } from "../composerDraftStore"; import { appendTerminalContextsToPrompt, formatTerminalContextLabel, type TerminalContextDraft, type TerminalContextSelection, } from "../lib/terminalContext"; import { appendElementContextsToPrompt, type ElementContextDraft, formatElementContextLabel, } from "../lib/elementContext"; import { appendPreviewAnnotationPrompt } from "../lib/previewAnnotation"; import { appendReviewCommentsToPrompt, type ReviewCommentContext } from "../reviewCommentContext"; import { environmentCatalog } from "../connection/catalog"; import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../terminalUiStateStore"; import { useKnownTerminalSessions, useThreadRunningTerminalIds } from "../state/terminalSessions"; import { projectEnvironment } from "../state/projects"; import { useEnvironmentQuery } from "../state/query"; import { primaryServerAvailableEditorsAtom, primaryServerKeybindingsAtom, primaryServerSettingsAtom, serverEnvironment, } from "../state/server"; import { terminalEnvironment } from "../state/terminal"; import { threadEnvironment } from "../state/threads"; import { vcsEnvironment } from "../state/vcs"; import { useEnvironments, usePrimaryEnvironment } from "../state/environments"; import { useProject, useProjects, useThread, useThreadProposedPlans, useThreadRefs, useThreadShell, } from "../state/entities"; import { environmentShell } from "../state/shell"; import { ChatComposer, type ChatComposerHandle } from "./chat/ChatComposer"; import { DraftHeroHeadline } from "./chat/DraftHeroHeadline"; import { ExpandedImageDialog } from "./chat/ExpandedImageDialog"; import { PullRequestThreadDialog } from "./PullRequestThreadDialog"; import { MessagesTimeline } from "./chat/MessagesTimeline"; import { ChatHeader } from "./chat/ChatHeader"; import { PanelLayoutControls, RightPanelMaximizeControl } from "./chat/PanelLayoutControls"; import { type ExpandedImagePreview } from "./chat/ExpandedImagePreview"; import { NoActiveThreadState } from "./NoActiveThreadState"; import { resolveEffectiveEnvMode, resolveLocalCheckoutBranchMismatch } from "./BranchToolbar.logic"; import { getProviderStatusBannerKey, ProviderStatusBanner, shouldShowProviderStatusBanner, } from "./chat/ProviderStatusBanner"; import { ThreadErrorBanner } from "./chat/ThreadErrorBanner"; import { resolveThreadPr } from "./ThreadStatusIndicators"; import { ComposerBannerStack, type ComposerBannerStackItem } from "./chat/ComposerBannerStack"; import { DRAFT_HERO_TRANSITION_ANIMATION_ID, DRAFT_HERO_TRANSITION_DURATION_MS, DRAFT_HERO_TRANSITION_EASING, MOBILE_COMPOSER_VIEW_TRANSITION_NAME, MOBILE_DRAFT_HEADLINE_VIEW_TRANSITION_NAME, runMobileComposerTransition, } from "./chat/draftHeroTransition"; import { MAX_HIDDEN_MOUNTED_TERMINAL_THREADS, branchMismatchKey, buildExpiredTerminalContextToastCopy, buildLocalDraftThread, buildThreadTurnInterruptInput, collectUserMessageBlobPreviewUrls, createLocalDispatchSnapshot, deriveComposerSendState, dismissBranchMismatchForSession, hasServerAcknowledgedLocalDispatch, isBranchMismatchDismissedForSession, shouldShowBranchMismatchBanner, getStartedThreadModelChangeBlockReason, LAST_INVOKED_SCRIPT_BY_PROJECT_KEY, LastInvokedScriptByProjectSchema, type LocalDispatchSnapshot, PullRequestDialogState, cloneComposerImageForRetry, deriveLockedProvider, readFileAsDataUrl, reconcileMountedTerminalThreadIds, resolveThreadMetadataUpdateForNextTurn, resolveSendEnvMode, revokeBlobPreviewUrl, revokeUserMessagePreviewUrls, waitForStartedServerThread, } from "./ChatView.logic"; import { useLocalStorage } from "~/hooks/useLocalStorage"; import { useComposerHandleContext } from "../composerHandleContext"; import { sanitizeThreadErrorMessage } from "~/rpc/transportError"; import { RightPanelSheet } from "./RightPanelSheet"; import { previewEnvironment } from "../state/preview"; import { useAtomCommand } from "../state/use-atom-command"; import { Button } from "./ui/button"; import { AlertDialog, AlertDialogClose, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogPopup, AlertDialogTitle, } from "./ui/alert-dialog"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; import { ServerUpdateAction } from "./ServerUpdateAction"; import { buildVersionMismatchDismissalKey, dismissVersionMismatch, isVersionMismatchDismissed, resolveServerConfigVersionMismatch, resolveServerSelfUpdateCapability, serverUpdateGuidance, } from "../versionSkew"; import { useAssetUrls } from "../assets/assetUrls"; const IMAGE_ONLY_BOOTSTRAP_PROMPT = "[User attached one or more images without additional text. Respond using the conversation context and the attached image(s).]"; const EMPTY_ACTIVITIES: OrchestrationThreadActivity[] = []; const EMPTY_PROVIDERS: ServerProvider[] = []; const EMPTY_PROVIDER_SKILLS: ServerProvider["skills"] = []; const EMPTY_PENDING_USER_INPUT_ANSWERS: Record<string, PendingUserInputDraftAnswer> = {}; function useDraftHeroLayoutTransition(isDraftHeroState: boolean) { const transitionGroupRef = useRef<HTMLDivElement | null>(null); const composerAnchorRef = useRef<HTMLDivElement | null>(null); const previousStateRef = useRef(isDraftHeroState); const previousComposerRectRef = useRef<DOMRect | null>(null); const animationRef = useRef<Animation | null>(null); const attachTransitionGroupRef = (element: HTMLDivElement | null) => { transitionGroupRef.current = element; }; const attachComposerAnchorRef = (element: HTMLDivElement | null) => { composerAnchorRef.current = element; }; const captureComposerRect = () => { previousComposerRectRef.current = composerAnchorRef.current?.getBoundingClientRect() ?? null; }; useLayoutEffect(() => { const transitionGroup = transitionGroupRef.current; const nextComposerRect = composerAnchorRef.current?.getBoundingClientRect() ?? null; const stateChanged = previousStateRef.current !== isDraftHeroState; const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches; const mobileComposerTransitionActive = typeof document !== "undefined" && document.documentElement.dataset.mobileComposerRouteTransition === "true"; animationRef.current?.cancel(); animationRef.current = null; const previousComposerRect = previousComposerRectRef.current; if ( stateChanged && !prefersReducedMotion && !mobileComposerTransitionActive && transitionGroup && previousComposerRect && nextComposerRect && typeof transitionGroup.animate === "function" ) { const translateX = previousComposerRect.left - nextComposerRect.left; const translateY = previousComposerRect.top - nextComposerRect.top; if (Math.abs(translateX) >= 0.5 || Math.abs(translateY) >= 0.5) { const animation = transitionGroup.animate( [ { transform: `translate3d(${translateX}px, ${translateY}px, 0)` }, { transform: "translate3d(0, 0, 0)" }, ], { duration: DRAFT_HERO_TRANSITION_DURATION_MS, easing: DRAFT_HERO_TRANSITION_EASING, }, ); animation.id = DRAFT_HERO_TRANSITION_ANIMATION_ID; animationRef.current = animation; void animation.finished .catch(() => undefined) .then(() => { if (animationRef.current !== animation) { return; } animationRef.current = null; }); } } previousStateRef.current = isDraftHeroState; previousComposerRectRef.current = nextComposerRect; }, [isDraftHeroState]); return [attachTransitionGroupRef, attachComposerAnchorRef, captureComposerRect] as const; } const PreviewPanel = lazy(() => import("./preview/PreviewPanel").then((module) => ({ default: module.PreviewPanel })), ); const DiffPanel = lazy(() => import("./DiffPanel")); const FilePreviewPanel = lazy(() => import("./files/FilePreviewPanel")); const EMPTY_PENDING_FILE_SURFACE_IDS: ReadonlySet<string> = new Set(); const TYPE_TO_FOCUS_EDITABLE_SELECTOR = [ "input", "textarea", "select", '[contenteditable="true"]', '[contenteditable="plaintext-only"]', '[role="textbox"]', ].join(","); const TYPE_TO_FOCUS_INTERACTIVE_SELECTOR = [ "button", "a[href]", "summary", '[role="button"]', '[role="checkbox"]', '[role="menuitem"]', '[role="option"]', '[role="radio"]', '[role="switch"]', '[role="tab"]', ].join(","); const TYPE_TO_FOCUS_FLOATING_LAYER_SELECTOR = [ '[data-slot="dialog"]', '[data-slot="menu-popup"]', '[data-slot="select-popup"]', '[data-slot="popover-popup"]', '[data-slot="combobox-popup"]', '[data-slot="autocomplete-popup"]', ].join(","); type EnvironmentUnavailableState = { readonly environmentId: EnvironmentId; readonly label: string; readonly connection: EnvironmentConnectionPresentation; }; type ThreadPlanCatalogEntry = Pick<Thread, "id" | "proposedPlans">; function eventPathContainsSelector(event: Event, selector: string): boolean { const path = event.composedPath(); if (path.length === 0 && event.target) { path.push(event.target); } return path.some((target) => target instanceof Element && target.closest(selector)); } function shouldTypeToFocusComposer(event: KeyboardEvent): boolean { if (event.defaultPrevented || event.isComposing) return false; if (event.metaKey || event.ctrlKey || event.altKey) return false; if (event.key.length !== 1) return false; if (eventPathContainsSelector(event, TYPE_TO_FOCUS_EDITABLE_SELECTOR)) return false; if (eventPathContainsSelector(event, TYPE_TO_FOCUS_INTERACTIVE_SELECTOR)) return false; if (document.querySelector(TYPE_TO_FOCUS_FLOATING_LAYER_SELECTOR)) return false; return true; } function formatOutgoingPrompt(params: { provider: ProviderDriverKind; model: string | null; models: ReadonlyArray<ServerProvider["models"][number]>; effort: string | null; text: string; }): string { const caps = getProviderModelCapabilities(params.models, params.model, params.provider); const promptEffort = resolvePromptInjectedEffort(caps, params.effort); return applyClaudePromptEffortPrefix(params.text, promptEffort); } const SCRIPT_TERMINAL_COLS = 120; const SCRIPT_TERMINAL_ROWS = 30; type ChatViewProps = | { environmentId: EnvironmentId; threadId: ThreadId; onDiffPanelOpen?: () => void; reserveTitleBarControlInset?: boolean; forceExpandedMobileComposer?: boolean; routeKind: "server"; draftId?: never; } | { environmentId: EnvironmentId; threadId: ThreadId; onDiffPanelOpen?: () => void; reserveTitleBarControlInset?: boolean; forceExpandedMobileComposer?: boolean; routeKind: "draft"; draftId: DraftId; }; interface TerminalLaunchContext { threadId: ThreadId; cwd: string; worktreePath: string | null; } type PersistentTerminalLaunchContext = Pick<TerminalLaunchContext, "cwd" | "worktreePath">; function useLocalDispatchState(input: { activeThread: Thread | undefined; activeLatestTurn: Thread["latestTurn"] | null; phase: SessionPhase; activePendingApproval: ApprovalRequestId | null; activePendingUserInput: ApprovalRequestId | null; threadError: string | null | undefined; }) { const [localDispatch, setLocalDispatch] = useState<LocalDispatchSnapshot | null>(null); const latestUserMessageId = input.activeThread?.messages.findLast((message) => message.role === "user")?.id ?? null; const resetLocalDispatch = useCallback(() => { setLocalDispatch(null); }, []); const serverAcknowledgedLocalDispatch = useMemo( () => hasServerAcknowledgedLocalDispatch({