import { type EnvironmentId } from "@t3tools/contracts";import { memo } from "react";import { readLocalApi } from "~/localApi"; import { cn } from "~/lib/utils";import { ProjectFavicon } from "../ProjectFavicon";import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; interface ChatHeaderProps { activeThreadEnvironmentId: EnvironmentId; activeThreadTitle: string; activeProjectName: string | undefined; activeProjectCwd: string | null; activeProjectFaviconPath: string | null; activeProjectIcon: import("@t3tools/contracts").ProjectIconOverride | null; rightPanelOpen: boolean; onNewThreadInProject: () => void; onOpenProjectSettings?: (() => void) | undefined;} export const ChatHeader = memo(function ChatHeader({ activeThreadEnvironmentId, activeThreadTitle, activeProjectName, activeProjectCwd, activeProjectFaviconPath, activeProjectIcon, rightPanelOpen, onNewThreadInProject, onOpenProjectSettings,}: ChatHeaderProps) { return ( <div onContextMenu={ onOpenProjectSettings === undefined ? undefined : (event) => { event.preventDefault(); const api = readLocalApi(); if (!api) return; void api.contextMenu .show([{ id: "project-settings", label: "Project settings", icon: "settings" }], { x: event.clientX, y: event.clientY, }) .then((action) => { if (action === "project-settings") onOpenProjectSettings(); }); } } className={cn( "flex min-w-0 flex-1 items-center gap-2 sm:gap-3", rightPanelOpen ? "pr-10" : "pr-24", )} > <div className="flex min-w-0 flex-1 items-center gap-2 overflow-hidden sm:gap-3"> {/* The project always leads the header: knowing which project a thread lives in is priority zero, and the thread title alone doesn't answer it. */} {activeProjectName ? ( <span className="inline-flex shrink-0 items-center gap-2"> <Tooltip> <TooltipTrigger render={ <button type="button" aria-label={`New thread in ${activeProjectName}`} onClick={onNewThreadInProject} className="inline-flex min-w-0 cursor-pointer items-center gap-1.5 rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring" /> } > <ProjectFavicon environmentId={activeThreadEnvironmentId} cwd={activeProjectCwd ?? ""} faviconPath={activeProjectFaviconPath} projectIcon={activeProjectIcon} projectName={activeProjectName} className="size-3.5" /> <span className="max-w-40 truncate text-sm font-medium">{activeProjectName}</span> </TooltipTrigger> <TooltipPopup side="top">New thread in {activeProjectName}</TooltipPopup> </Tooltip> <span aria-hidden className="text-muted-foreground/40"> / </span> </span> ) : null} <Tooltip> <TooltipTrigger render={ <h2 aria-label={activeThreadTitle} className="min-w-0 flex-1 truncate text-sm font-medium text-foreground" > {activeThreadTitle} </h2> } /> <TooltipPopup side="top">{activeThreadTitle}</TooltipPopup> </Tooltip> </div> </div> );});
import { type EnvironmentId } from "@t3tools/contracts";
import { memo } from "react";
import { readLocalApi } from "~/localApi";
import { cn } from "~/lib/utils";
import { ProjectFavicon } from "../ProjectFavicon";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
interface ChatHeaderProps {
activeThreadEnvironmentId: EnvironmentId;
activeThreadTitle: string;
activeProjectName: string | undefined;
activeProjectCwd: string | null;
activeProjectFaviconPath: string | null;
activeProjectIcon: import("@t3tools/contracts").ProjectIconOverride | null;
rightPanelOpen: boolean;
onNewThreadInProject: () => void;
onOpenProjectSettings?: (() => void) | undefined;
}
export const ChatHeader = memo(function ChatHeader({
activeThreadEnvironmentId,
activeThreadTitle,
activeProjectName,
activeProjectCwd,
activeProjectFaviconPath,
activeProjectIcon,
rightPanelOpen,
onNewThreadInProject,
onOpenProjectSettings,
}: ChatHeaderProps) {
return (
<div
onContextMenu={
onOpenProjectSettings === undefined
? undefined
: (event) => {
event.preventDefault();
const api = readLocalApi();
if (!api) return;
void api.contextMenu
.show([{ id: "project-settings", label: "Project settings", icon: "settings" }], {
x: event.clientX,
y: event.clientY,
})
.then((action) => {
if (action === "project-settings") onOpenProjectSettings();
});
}
}
className={cn(
"flex min-w-0 flex-1 items-center gap-2 sm:gap-3",
rightPanelOpen ? "pr-10" : "pr-24",
)}
>
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-hidden sm:gap-3">
{/* The project always leads the header: knowing which project a
thread lives in is priority zero, and the thread title alone
doesn't answer it. */}
{activeProjectName ? (
<span className="inline-flex shrink-0 items-center gap-2">
<Tooltip>
<TooltipTrigger
render={
<button
type="button"
aria-label={`New thread in ${activeProjectName}`}
onClick={onNewThreadInProject}
className="inline-flex min-w-0 cursor-pointer items-center gap-1.5 rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
/>
}
>
<ProjectFavicon
environmentId={activeThreadEnvironmentId}
cwd={activeProjectCwd ?? ""}
faviconPath={activeProjectFaviconPath}
projectIcon={activeProjectIcon}
projectName={activeProjectName}
className="size-3.5"
/>
<span className="max-w-40 truncate text-sm font-medium">{activeProjectName}</span>
</TooltipTrigger>
<TooltipPopup side="top">New thread in {activeProjectName}</TooltipPopup>
</Tooltip>
<span aria-hidden className="text-muted-foreground/40">
/
</span>
</span>
) : null}
<Tooltip>
<TooltipTrigger
render={
<h2
aria-label={activeThreadTitle}
className="min-w-0 flex-1 truncate text-sm font-medium text-foreground"
>
{activeThreadTitle}
</h2>
}
/>
<TooltipPopup side="top">{activeThreadTitle}</TooltipPopup>
</Tooltip>
</div>
</div>
);
});