185 lines · 7.1 KB · TypeScript
RawDownload
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 <HostedStaticOnboardingState />;  }   return <IndexDraftLanding />;} /** * 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 ? (      <DraftStartError        onRetry={() => {          setStartState((state) => ({            failed: false,            retryRequest: state.retryRequest + 1,          }));        }}      />    ) : null;  }  return <NoProjectsHero />;} function DraftStartError({ onRetry }: { readonly onRetry: () => void }) {  return (    <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground">      <Empty className="flex-1">        <EmptyHeader className="max-w-md">          <EmptyTitle className="text-foreground text-xl">Couldn’t start a new thread</EmptyTitle>          <EmptyDescription className="mt-2 text-sm text-muted-foreground/78">            The project is still available. Try opening the draft again.          </EmptyDescription>          <div className="mt-5 flex justify-center">            <Button size="sm" onClick={onRetry}>              <RotateCcwIcon className="size-4" />              Try again            </Button>          </div>        </EmptyHeader>      </Empty>    </SidebarInset>  );} function NoProjectsHero() {  const openAddProject = useCallback(() => openCommandPalette({ open: "add-project" }), []);   return (    <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground">      <div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-background">        <Empty className="flex-1">          <div className="w-full max-w-lg px-8 py-12">            <EmptyHeader className="max-w-none">              <EmptyTitle className="text-foreground text-2xl sm:text-3xl">                What should we work on?              </EmptyTitle>              <EmptyDescription className="mt-2 text-sm text-muted-foreground/78">                Add a project to start your first thread.              </EmptyDescription>              <div className="mt-6 flex justify-center">                <Button size="sm" onClick={openAddProject}>                  <PlusIcon className="size-4" />                  Add project                </Button>              </div>            </EmptyHeader>          </div>        </Empty>      </div>    </SidebarInset>  );} export const Route = createFileRoute("/_chat/")({  component: ChatIndexRouteView,}); function HostedStaticOnboardingState() {  const cloudEnabled = hasCloudPublicConfig();   return (    <SidebarInset className="h-dvh min-h-0 overflow-hidden overscroll-y-none bg-background text-foreground">      <div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-background">        <header          className={cn(            "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center border-b border-border px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5",            COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS,          )}        >          <div className="flex items-center gap-2">            <span className="text-sm font-medium text-foreground md:text-muted-foreground/60">              {APP_DISPLAY_NAME}            </span>          </div>        </header>         <Empty className="flex-1">          <div className="w-full max-w-xl rounded-3xl border border-border/55 bg-card/20 px-8 py-12 shadow-sm/5">            <EmptyHeader className="max-w-none">              <div className="mx-auto mb-5 flex size-11 items-center justify-center rounded-xl border border-border/70 bg-background/70 text-muted-foreground">                <LinkIcon className="size-5" />              </div>              <EmptyTitle className="text-foreground text-xl">                Connect an environment to get started              </EmptyTitle>              <EmptyDescription className="mt-2 text-sm leading-relaxed text-muted-foreground/78">                {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."}              </EmptyDescription>              <div className="mt-6 flex justify-center">                <Button render={<Link to="/settings/connections" />} size="sm">                  <PlusIcon className="size-4" />                  {cloudEnabled ? "Open Connections" : "Add environment"}                </Button>              </div>            </EmptyHeader>          </div>        </Empty>      </div>    </SidebarInset>  );}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t