import { memo, useCallback, useEffect, useState, type MouseEvent } from "react";
import { styled, useStyletron } from "baseui";
import { GitPullRequest, GitPullRequestDraft } from "lucide-react";
import { useFoundryTokens } from "../../app/theme";
import { getFoundryTokens } from "../../styles/tokens";
import type { AgentKind, AgentSession } from "./view-model";
export interface ContextMenuItem {
label: string;
onClick: () => void;
}
export function useContextMenu() {
const [menu, setMenu] = useState<{ x: number; y: number; items: ContextMenuItem[] } | null>(null);
useEffect(() => {
if (!menu) {
return;
}
const close = () => setMenu(null);
window.addEventListener("click", close);
window.addEventListener("contextmenu", close);
return () => {
window.removeEventListener("click", close);
window.removeEventListener("contextmenu", close);
};
}, [menu]);
const open = useCallback((event: MouseEvent, items: ContextMenuItem[]) => {
event.preventDefault();
event.stopPropagation();
setMenu({ x: event.clientX, y: event.clientY, items });
}, []);
return { menu, open, close: useCallback(() => setMenu(null), []) };
}
export const ContextMenuOverlay = memo(function ContextMenuOverlay({
menu,
onClose,
}: {
menu: { x: number; y: number; items: ContextMenuItem[] };
onClose: () => void;
}) {
const [css] = useStyletron();
const t = useFoundryTokens();
return (
{menu.items.map((item, index) => (
{
item.onClick();
onClose();
}}
className={css({
padding: "8px 14px",
fontSize: "12px",
color: t.textPrimary,
cursor: "pointer",
":hover": { backgroundColor: t.interactiveHover },
})}
>
{item.label}
))}
);
});
export const SpinnerDot = memo(function SpinnerDot({ size = 10 }: { size?: number }) {
const t = useFoundryTokens();
return (
);
});
export const UnreadDot = memo(function UnreadDot() {
const t = useFoundryTokens();
return (
);
});
export const TaskIndicator = memo(function TaskIndicator({
isRunning,
isProvisioning,
hasUnread,
isDraft,
}: {
isRunning: boolean;
isProvisioning: boolean;
hasUnread: boolean;
isDraft: boolean;
}) {
const t = useFoundryTokens();
if (isRunning) return ;
if (isProvisioning) return ;
if (hasUnread) return ;
if (isDraft) return ;
return ;
});
const ClaudeIcon = memo(function ClaudeIcon({ size = 14 }: { size?: number }) {
return (
);
});
const OpenAIIcon = memo(function OpenAIIcon({ size = 14 }: { size?: number }) {
const t = useFoundryTokens();
return (
);
});
const CursorIcon = memo(function CursorIcon({ size = 14 }: { size?: number }) {
const t = useFoundryTokens();
return (
);
});
export const AgentIcon = memo(function AgentIcon({ agent, size = 14 }: { agent: AgentKind; size?: number }) {
switch (agent) {
case "Claude":
return ;
case "Codex":
return ;
case "Cursor":
return ;
default:
return ;
}
});
export type HeaderStatusVariant = "error" | "warning" | "success" | "neutral";
export interface HeaderStatusInfo {
variant: HeaderStatusVariant;
label: string;
spinning: boolean;
tooltip?: string;
}
export const HeaderStatusPill = memo(function HeaderStatusPill({ status }: { status: HeaderStatusInfo }) {
const [css] = useStyletron();
const t = useFoundryTokens();
const colorMap: Record = {
error: { bg: `${t.statusError}18`, text: t.statusError, dot: t.statusError },
warning: { bg: `${t.statusWarning}18`, text: t.statusWarning, dot: t.statusWarning },
success: { bg: `${t.statusSuccess}18`, text: t.statusSuccess, dot: t.statusSuccess },
neutral: { bg: t.interactiveSubtle, text: t.textTertiary, dot: t.textTertiary },
};
const colors = colorMap[status.variant];
return (
{status.spinning ? (
) : (
)}
{status.label}
);
});
export const SessionAvatar = memo(function SessionAvatar({ session }: { session: AgentSession }) {
if (session.status === "running" || session.status === "pending_provision" || session.status === "pending_session_create") return ;
if (session.unread) return ;
return ;
});
export const Shell = styled("div", ({ $theme }) => {
const t = getFoundryTokens($theme);
return {
display: "flex",
height: "100dvh",
backgroundColor: t.surfaceSecondary,
overflow: "hidden",
};
});
export const SPanel = styled("section", ({ $theme }) => {
const t = getFoundryTokens($theme);
return {
minHeight: 0,
flex: 1,
display: "flex",
flexDirection: "column" as const,
backgroundColor: t.surfaceSecondary,
overflow: "hidden",
};
});
export const ScrollBody = styled("div", () => ({
minHeight: 0,
flex: 1,
position: "relative" as const,
overflowY: "auto" as const,
display: "flex",
flexDirection: "column" as const,
}));
export const HEADER_HEIGHT = "42px";
export const PROMPT_TEXTAREA_MIN_HEIGHT = 56;
export const PROMPT_TEXTAREA_MAX_HEIGHT = 100;
export const PanelHeaderBar = styled("div", ({ $theme }) => {
const t = getFoundryTokens($theme);
return {
display: "flex",
alignItems: "center",
minHeight: HEADER_HEIGHT,
maxHeight: HEADER_HEIGHT,
paddingTop: "0",
paddingRight: "14px",
paddingBottom: "0",
paddingLeft: "14px",
borderBottom: `1px solid ${t.borderDefault}`,
backgroundColor: t.surfaceTertiary,
gap: "8px",
flexShrink: 0,
position: "relative" as const,
zIndex: 9999,
};
});