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
219 lines
7.5 KiB
TypeScript
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>
|
|
);
|
|
}
|