Files
moreminimore-vibe/src/components/chat/ChatHeader.tsx
Will Chen b0f08eaf15 Neon / portal template support (#713)
TODOs:
- [x] Do restart when checkout / restore if there is a DB
- [x] List all branches (branch id, name, date)
- [x] Allow checking out versions with no DB
- [x] safeguard to never delete main branches
- [x] create app hook for neon template
- [x] weird UX with connector on configure panel
- [x] tiny neon logo in connector
- [x] deploy to vercel
- [x] build forgot password page
- [x] what about email setup
- [x] lots of imgix errors
- [x] edit file - db snapshot
- [x] DYAD_DISABLE_DB_PUSH
- [ ] update portal doc
- [x] switch preview branch to be read-only endpoint
- [x] disable supabase sys prompt if neon is enabled
- [ ] https://payloadcms.com/docs/upload/storage-adapters
- [x] need to use main branch...

Phase 2?
- [x] generate DB migrations
2025-08-04 16:36:09 -07:00

219 lines
7.5 KiB
TypeScript

import {
PanelRightOpen,
History,
PlusCircle,
GitBranch,
Info,
} from "lucide-react";
import { PanelRightClose } from "lucide-react";
import { useAtom, useAtomValue } from "jotai";
import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { useVersions } from "@/hooks/useVersions";
import { Button } from "../ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip";
import { IpcClient } from "@/ipc/ipc_client";
import { useRouter } from "@tanstack/react-router";
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { useChats } from "@/hooks/useChats";
import { showError, showSuccess } from "@/lib/toast";
import { useEffect } from "react";
import { useStreamChat } from "@/hooks/useStreamChat";
import { useCurrentBranch } from "@/hooks/useCurrentBranch";
import { useCheckoutVersion } from "@/hooks/useCheckoutVersion";
import { useRenameBranch } from "@/hooks/useRenameBranch";
import { isAnyCheckoutVersionInProgressAtom } from "@/store/appAtoms";
import { LoadingBar } from "../ui/LoadingBar";
interface ChatHeaderProps {
isVersionPaneOpen: boolean;
isPreviewOpen: boolean;
onTogglePreview: () => void;
onVersionClick: () => void;
}
export function ChatHeader({
isVersionPaneOpen,
isPreviewOpen,
onTogglePreview,
onVersionClick,
}: ChatHeaderProps) {
const appId = useAtomValue(selectedAppIdAtom);
const { versions, loading: versionsLoading } = useVersions(appId);
const { navigate } = useRouter();
const [selectedChatId, setSelectedChatId] = useAtom(selectedChatIdAtom);
const { refreshChats } = useChats(appId);
const { isStreaming } = useStreamChat();
const isAnyCheckoutVersionInProgress = useAtomValue(
isAnyCheckoutVersionInProgressAtom,
);
const {
branchInfo,
isLoading: branchInfoLoading,
refetchBranchInfo,
} = useCurrentBranch(appId);
const { checkoutVersion, isCheckingOutVersion } = useCheckoutVersion();
const { renameBranch, isRenamingBranch } = useRenameBranch();
useEffect(() => {
if (appId) {
refetchBranchInfo();
}
}, [appId, selectedChatId, isStreaming, refetchBranchInfo]);
const handleCheckoutMainBranch = async () => {
if (!appId) return;
await checkoutVersion({ appId, versionId: "main" });
};
const handleRenameMasterToMain = async () => {
if (!appId) return;
// If this throws, it will automatically show an error toast
await renameBranch({ oldBranchName: "master", newBranchName: "main" });
showSuccess("Master branch renamed to main");
};
const handleNewChat = async () => {
if (appId) {
try {
const chatId = await IpcClient.getInstance().createChat(appId);
setSelectedChatId(chatId);
navigate({
to: "/chat",
search: { id: chatId },
});
await refreshChats();
} catch (error) {
showError(`Failed to create new chat: ${(error as any).toString()}`);
}
} else {
navigate({ to: "/" });
}
};
// REMINDER: KEEP UP TO DATE WITH app_handlers.ts
const versionPostfix = versions.length === 100_000 ? `+` : "";
const isNotMainBranch = branchInfo && branchInfo.branch !== "main";
const currentBranchName = branchInfo?.branch;
return (
<div className="flex flex-col w-full @container">
<LoadingBar isVisible={isAnyCheckoutVersionInProgress} />
{/* If the version pane is open, it's expected to not always be on the main branch. */}
{isNotMainBranch && !isVersionPaneOpen && (
<div className="flex flex-col @sm:flex-row items-center justify-between px-4 py-2 bg-amber-100 dark:bg-amber-900 text-amber-800 dark:text-amber-200">
<div className="flex items-center gap-2 text-sm">
<GitBranch size={16} />
<span>
{currentBranchName === "<no-branch>" && (
<>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span className="flex items-center gap-1">
{isAnyCheckoutVersionInProgress ? (
<>
<span>
Please wait, switching back to latest version...
</span>
</>
) : (
<>
<strong>Warning:</strong>
<span>You are not on a branch</span>
<Info size={14} />
</>
)}
</span>
</TooltipTrigger>
<TooltipContent>
<p>
{isAnyCheckoutVersionInProgress
? "Version checkout is currently in progress"
: "Checkout main branch, otherwise changes will not be saved properly"}
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</>
)}
{currentBranchName && currentBranchName !== "<no-branch>" && (
<span>
You are on branch: <strong>{currentBranchName}</strong>.
</span>
)}
{branchInfoLoading && <span>Checking branch...</span>}
</span>
</div>
{currentBranchName === "master" ? (
<Button
variant="outline"
size="sm"
onClick={handleRenameMasterToMain}
disabled={isRenamingBranch || branchInfoLoading}
>
{isRenamingBranch ? "Renaming..." : "Rename master to main"}
</Button>
) : isAnyCheckoutVersionInProgress && !isCheckingOutVersion ? null : (
<Button
variant="outline"
size="sm"
onClick={handleCheckoutMainBranch}
disabled={isCheckingOutVersion || branchInfoLoading}
>
{isCheckingOutVersion
? "Checking out..."
: "Switch to main branch"}
</Button>
)}
</div>
)}
{/* Why is this pt-0.5? Because the loading bar is h-1 (it always takes space) and we want the vertical spacing to be consistent.*/}
<div className="@container flex items-center justify-between pb-1.5 pt-0.5">
<div className="flex items-center space-x-2">
<Button
onClick={handleNewChat}
variant="ghost"
className="hidden @2xs:flex items-center justify-start gap-2 mx-2 py-3"
>
<PlusCircle size={16} />
<span>New Chat</span>
</Button>
<Button
onClick={onVersionClick}
variant="ghost"
className="hidden @6xs:flex cursor-pointer items-center gap-1 text-sm px-2 py-1 rounded-md"
>
<History size={16} />
{versionsLoading
? "..."
: `Version ${versions.length}${versionPostfix}`}
</Button>
</div>
<button
data-testid="toggle-preview-panel-button"
onClick={onTogglePreview}
className="cursor-pointer p-2 hover:bg-(--background-lightest) rounded-md"
>
{isPreviewOpen ? (
<PanelRightClose size={20} />
) : (
<PanelRightOpen size={20} />
)}
</button>
</div>
</div>
);
}