import { scopeProjectRef } from "@t3tools/client-runtime/environment"; import { createFileRoute, Link } from "@tanstack/react-router"; import { LinkIcon, PlusIcon, RotateCcwIcon } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { openCommandPalette } from "../commandPaletteBus"; import { sortScopedProjectsForSidebar } from "../components/Sidebar.logic"; import { Button } from "../components/ui/button"; import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "../components/ui/empty"; import { SidebarInset } from "../components/ui/sidebar"; import { useNewThreadHandler } from "../hooks/useHandleNewThread"; import { useAllEnvironmentShellsBootstrapped, useProjects, useThreadShells, } from "../state/entities"; import { useEnvironments } from "../state/environments"; import { APP_DISPLAY_NAME } from "~/branding"; import { hasCloudPublicConfig } from "~/cloud/publicConfig"; import { cn } from "~/lib/utils"; import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "~/workspaceTitlebar"; function ChatIndexRouteView() { const { authGateState } = Route.useRouteContext(); const { environments } = useEnvironments(); if (authGateState.status === "hosted-static" && environments.length === 0) { return ; } return ; } /** * Landing on the index route drops straight into a draft thread for the most * recently active project, so the first screen is a prompt instead of a dead * end. Falls back to an add-project hero when no project exists yet. */ function IndexDraftLanding() { const projects = useProjects(); const threads = useThreadShells(); const bootstrapped = useAllEnvironmentShellsBootstrapped(); const handleNewThread = useNewThreadHandler(); const startingRef = useRef(false); const [startState, setStartState] = useState({ failed: false, retryRequest: 0 }); const mostRecentProject = useMemo( () => bootstrapped ? (sortScopedProjectsForSidebar(projects, threads, "updated_at")[0] ?? null) : null, [bootstrapped, projects, threads], ); useEffect(() => { if (mostRecentProject === null || startingRef.current) { return; } startingRef.current = true; void handleNewThread(scopeProjectRef(mostRecentProject.environmentId, mostRecentProject.id), { replace: true, }).catch(() => { startingRef.current = false; setStartState((state) => ({ ...state, failed: true })); }); }, [handleNewThread, mostRecentProject, startState.retryRequest]); if (!bootstrapped) { return null; } if (mostRecentProject !== null) { return startState.failed ? ( { setStartState((state) => ({ failed: false, retryRequest: state.retryRequest + 1, })); }} /> ) : null; } return ; } function DraftStartError({ onRetry }: { readonly onRetry: () => void }) { return ( Couldn’t start a new thread The project is still available. Try opening the draft again. Try again ); } function NoProjectsHero() { const openAddProject = useCallback(() => openCommandPalette({ open: "add-project" }), []); return ( What should we work on? Add a project to start your first thread. Add project ); } export const Route = createFileRoute("/_chat/")({ component: ChatIndexRouteView, }); function HostedStaticOnboardingState() { const cloudEnabled = hasCloudPublicConfig(); return ( {APP_DISPLAY_NAME} Connect an environment to get started {cloudEnabled ? "Sign in to T3 Connect to connect a linked environment through its managed tunnel, or add a reachable backend manually." : "Add a reachable backend manually to start working from this browser."} } size="sm"> {cloudEnabled ? "Open Connections" : "Add environment"} ); }