Fix: added header

This commit is contained in:
Harshith Mullapudi 2025-07-17 11:19:28 +05:30
parent 8658342168
commit 673809576c
14 changed files with 568 additions and 199 deletions

View File

@ -0,0 +1,137 @@
import { useNavigate } from "@remix-run/react";
import { Button } from "~/components/ui/button";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { SidebarTrigger } from "~/components/ui/sidebar";
export interface BreadcrumbItem {
label: string;
href?: string;
}
export interface PageHeaderAction {
label: string;
icon?: React.ReactNode;
onClick: () => void;
variant?: "default" | "secondary" | "outline" | "ghost";
}
export interface PageHeaderTab {
label: string;
value: string;
isActive: boolean;
onClick: () => void;
}
export interface PageHeaderProps {
title: string;
breadcrumbs?: BreadcrumbItem[];
actions?: PageHeaderAction[];
tabs?: PageHeaderTab[];
showBackForward?: boolean;
}
// Back and Forward navigation component
function NavigationBackForward() {
const navigate = useNavigate();
return (
<div className="mr-1 flex items-center gap-1">
<Button
variant="ghost"
size="xs"
aria-label="Back"
onClick={() => navigate(-1)}
className="rounded"
type="button"
>
<ArrowLeft size={16} />
</Button>
<Button
variant="ghost"
size="xs"
aria-label="Forward"
onClick={() => navigate(1)}
className="rounded"
type="button"
>
<ArrowRight size={16} />
</Button>
</div>
);
}
export function PageHeader({
title,
breadcrumbs,
actions,
tabs,
showBackForward = true,
}: PageHeaderProps) {
return (
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b border-gray-300 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
<div className="flex w-full items-center justify-between gap-1 px-4 pr-2 lg:gap-2">
<div className="-ml-1 flex items-center gap-1">
{/* Back/Forward navigation before SidebarTrigger */}
{showBackForward && <NavigationBackForward />}
<SidebarTrigger className="mr-1" />
{/* Breadcrumbs */}
{breadcrumbs && breadcrumbs.length > 0 ? (
<nav className="mt-0.5 flex items-center space-x-1">
{breadcrumbs.map((breadcrumb, index) => (
<div key={index} className="flex items-center">
{index > 0 && (
<span className="text-muted-foreground mx-1">/</span>
)}
{breadcrumb.href ? (
<a href={breadcrumb.href}>{breadcrumb.label}</a>
) : (
<span className="text-gray-900">{breadcrumb.label}</span>
)}
</div>
))}
</nav>
) : (
<h1 className="text-base">{title}</h1>
)}
{/* Tabs */}
{tabs && tabs.length > 0 && (
<div className="ml-2 flex items-center gap-0.5">
{tabs.map((tab) => (
<Button
key={tab.value}
size="sm"
variant="secondary"
className="rounded"
isActive={tab.isActive}
onClick={tab.onClick}
aria-current={tab.isActive ? "page" : undefined}
>
{tab.label}
</Button>
))}
</div>
)}
</div>
{/* Actions */}
{actions && actions.length > 0 && (
<div className="flex items-center gap-2">
{actions.map((action, index) => (
<Button
key={index}
onClick={action.onClick}
variant={action.variant || "secondary"}
className="gap-2"
>
{action.icon}
{action.label}
</Button>
))}
</div>
)}
</div>
</header>
);
}

View File

@ -5,6 +5,7 @@ import { UserTypeEnum } from "@core/types";
import { type ConversationHistory } from "@core/database";
import { cn } from "~/lib/utils";
import { extensionsForConversation } from "./editor-extensions";
import { skillExtension } from "../editor/skill-extension";
interface AIConversationItemProps {
conversationHistory: ConversationHistory;
@ -20,7 +21,7 @@ export const ConversationItem = ({
const id = `a${conversationHistory.id.replace(/-/g, "")}`;
const editor = useEditor({
extensions: [...extensionsForConversation],
extensions: [...extensionsForConversation, skillExtension],
editable: false,
content: conversationHistory.message,
});

View File

@ -0,0 +1,14 @@
import React from "react";
interface ConversationContextInterface {
conversationHistoryId: string;
// Used just in streaming
streaming?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
actionMessages?: Record<string, any>;
}
export const ConversationContext = React.createContext<
ConversationContextInterface | undefined
>(undefined);

View File

@ -0,0 +1 @@
export * from './skill-extension';

View File

@ -0,0 +1,61 @@
import { NodeViewWrapper } from "@tiptap/react";
import React from "react";
import { getIcon as iconUtil, type IconType } from "../../icon-utils";
import { ChevronDown, ChevronRight } from "lucide-react";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const SkillComponent = (props: any) => {
const id = props.node.attrs.id;
const name = props.node.attrs.name;
const agent = props.node.attrs.agent;
const [open, setOpen] = React.useState(false);
if (id === "undefined" || id === undefined || !name) {
return null;
}
const getIcon = () => {
const Icon = iconUtil(agent as IconType);
return <Icon size={18} className="rounded-sm" />;
};
const snakeToTitleCase = (input: string): string => {
if (!input) {
return "";
}
const words = input.split("_");
// First word: capitalize first letter
const firstWord =
words[0].charAt(0).toUpperCase() + words[0].slice(1).toLowerCase();
// Rest of the words: all lowercase
const restWords = words.slice(1).map((word) => word.toLowerCase());
// Join with spaces
return [firstWord, ...restWords].join(" ");
};
const getComponent = () => {
return (
<>
<div className="flex items-center gap-2 text-sm">
{getIcon()}
<span className="font-mono text-sm">{snakeToTitleCase(name)}</span>
</div>
<div className="px-0">
{!open ? <ChevronRight size={16} /> : <ChevronDown size={16} />}
</div>
</>
);
};
return (
<NodeViewWrapper className="inline w-fit">{getComponent()}</NodeViewWrapper>
);
};

View File

@ -0,0 +1,47 @@
import { ReactNodeViewRenderer, Node, mergeAttributes } from "@tiptap/react";
import { SkillComponent } from "./skill-component";
export const skillExtension = Node.create({
name: "skill",
group: "block",
atom: true,
selectable: false,
addAttributes() {
return {
id: {
default: undefined,
},
name: {
default: undefined,
},
agent: {
default: undefined,
},
};
},
parseHTML() {
return [
{
tag: "skill",
getAttrs: (element) => {
return {
id: element.getAttribute("id"),
name: element.getAttribute("name"),
agent: element.getAttribute("agent"),
};
},
},
];
},
renderHTML({ HTMLAttributes }) {
return ["skill", mergeAttributes(HTMLAttributes)];
},
addNodeView() {
return ReactNodeViewRenderer(SkillComponent);
},
});

View File

@ -4,7 +4,7 @@ import {
} from "@remix-run/server-runtime";
import { sort } from "fast-sort";
import { useParams, useRevalidator } from "@remix-run/react";
import { useParams, useRevalidator, useNavigate } from "@remix-run/react";
import {
requireUser,
requireUserId,
@ -25,6 +25,8 @@ import {
import { useTypedLoaderData } from "remix-typedjson";
import React from "react";
import { ScrollAreaWithAutoScroll } from "~/components/use-auto-scroll";
import { PageHeader } from "~/components/common/page-header";
import { Plus } from "lucide-react";
import { json } from "@remix-run/node";
import { env } from "~/env.server";
@ -84,6 +86,8 @@ export default function SingleConversation() {
const { conversationId } = useParams();
const revalidator = useRevalidator();
const navigate = useNavigate();
React.useEffect(() => {
if (run) {
setConversationResponse(run);
@ -129,49 +133,57 @@ export default function SingleConversation() {
}
return (
<ResizablePanelGroup direction="horizontal" className="!rounded-md">
<ResizableHandle className="w-1" />
<>
<PageHeader
title="Conversation"
breadcrumbs={[
{ label: "Conversations", href: "/home/conversation" },
{ label: conversation.title || "Untitled" },
]}
actions={[
{
label: "New conversation",
icon: <Plus size={14} />,
onClick: () => navigate("/home/conversation"),
variant: "secondary",
},
]}
/>
<ResizablePanel
collapsible
collapsedSize={0}
className="flex w-full flex-col"
>
<div className="relative flex h-[calc(100vh_-_70px)] w-full flex-col items-center justify-center overflow-auto">
<div className="flex h-[calc(100vh_-_56px)] w-full flex-col justify-end overflow-hidden">
<ScrollAreaWithAutoScroll>
{getConversations()}
{conversationResponse && (
<StreamingConversation
runId={conversationResponse.id}
token={conversationResponse.token}
afterStreaming={() => {
setConversationResponse(undefined);
revalidator.revalidate();
}}
apiURL={apiURL}
<div className="relative flex h-[calc(100vh_-_56px)] w-full flex-col items-center justify-center overflow-auto">
<div className="flex h-[calc(100vh_-_80px)] w-full flex-col justify-end overflow-hidden">
<ScrollAreaWithAutoScroll>
{getConversations()}
{conversationResponse && (
<StreamingConversation
runId={conversationResponse.id}
token={conversationResponse.token}
afterStreaming={() => {
setConversationResponse(undefined);
revalidator.revalidate();
}}
apiURL={apiURL}
/>
)}
</ScrollAreaWithAutoScroll>
<div className="flex w-full flex-col items-center">
<div className="w-full max-w-[97ch] px-1 pr-2">
{conversation?.status !== "need_approval" && (
<ConversationTextarea
conversationId={conversationId as string}
className="bg-background-3 w-full border-1 border-gray-300"
isLoading={
!!conversationResponse ||
conversation?.status === "running" ||
stopLoading
}
/>
)}
</ScrollAreaWithAutoScroll>
<div className="flex w-full flex-col items-center">
<div className="w-full max-w-[97ch] px-1 pr-2">
{conversation?.status !== "need_approval" && (
<ConversationTextarea
conversationId={conversationId as string}
className="bg-background-3 w-full border-1 border-gray-300"
isLoading={
!!conversationResponse ||
conversation?.status === "running" ||
stopLoading
}
/>
)}
</div>
</div>
</div>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</div>
</>
);
}

View File

@ -17,6 +17,7 @@ import {
CreateConversationSchema,
} from "~/services/conversation.server";
import { json } from "@remix-run/node";
import { PageHeader } from "~/components/common/page-header";
export async function loader({ request }: LoaderFunctionArgs) {
// Only return userId, not the heavy nodeLinks
@ -67,6 +68,9 @@ export default function Chat() {
const { user } = useTypedLoaderData<typeof loader>();
return (
<>{typeof window !== "undefined" && <ConversationNew user={user} />}</>
<>
<PageHeader title="Conversation" />
{typeof window !== "undefined" && <ConversationNew user={user} />}
</>
);
}

View File

@ -14,6 +14,7 @@ import { SearchBodyRequest } from "./search";
import { SearchService } from "~/services/search.server";
import { GraphVisualizationClient } from "~/components/graph/graph-client";
import { LoaderCircle } from "lucide-react";
import { PageHeader } from "~/components/common/page-header";
export async function action({ request }: ActionFunctionArgs) {
const userId = await requireUserId(request);
@ -84,18 +85,21 @@ export default function Dashboard() {
}, [userId]);
return (
<div className="home flex h-[calc(100vh_-_56px)] flex-col overflow-y-auto p-3 text-base">
<div className="flex grow items-center justify-center rounded">
{loading ? (
<div className="flex h-full w-full flex-col items-center justify-center">
<LoaderCircle size={18} className="mr-1 animate-spin" />
<span className="text-muted-foreground">Loading graph...</span>
</div>
) : (
typeof window !== "undefined" &&
nodeLinks && <GraphVisualizationClient triplets={nodeLinks} />
)}
<>
<PageHeader title="Memory graph" />
<div className="home flex h-[calc(100vh_-_56px)] flex-col overflow-y-auto p-3 text-base">
<div className="flex grow items-center justify-center rounded">
{loading ? (
<div className="flex h-full w-full flex-col items-center justify-center">
<LoaderCircle size={18} className="mr-1 animate-spin" />
<span className="text-muted-foreground">Loading graph...</span>
</div>
) : (
typeof window !== "undefined" &&
nodeLinks && <GraphVisualizationClient triplets={nodeLinks} />
)}
</div>
</div>
</div>
</>
);
}

View File

@ -20,6 +20,8 @@ import {
upsertIngestionRule,
} from "~/services/ingestionRule.server";
import { Section } from "~/components/integrations/section";
import { PageHeader } from "~/components/common/page-header";
import { Plus } from "lucide-react";
export async function loader({ request, params }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
@ -139,84 +141,105 @@ export default function IntegrationDetail() {
const Component = getIcon(integration.icon as IconType);
return (
<div className="overflow-hidden p-4 px-5">
<Section
title={integration.name}
description={integration.description}
icon={
<div className="bg-grayAlpha-100 flex h-12 w-12 items-center justify-center rounded">
<Component size={24} />
</div>
}
>
<div>
{/* Authentication Methods */}
<div className="space-y-4">
<h3 className="text-lg font-medium">Authentication Methods</h3>
<div className="space-y-2">
{hasApiKey && (
<div className="flex items-center gap-2">
<span className="inline-flex items-center gap-2 text-sm">
<Checkbox checked /> API Key authentication
</span>
</div>
)}
{hasOAuth2 && (
<div className="flex items-center gap-2">
<span className="inline-flex items-center gap-2 text-sm">
<Checkbox checked />
OAuth 2.0 authentication
</span>
</div>
)}
{!hasApiKey && !hasOAuth2 && !hasMCPAuth && (
<div className="text-muted-foreground text-sm">
No authentication method specified
</div>
)}
<div className="flex h-full flex-col overflow-hidden">
<PageHeader
title="Integrations"
breadcrumbs={[
{ label: "Integrations", href: "/home/integrations" },
{ label: integration?.name || "Untitled" },
]}
actions={[
{
label: "Request New Integration",
icon: <Plus size={14} />,
onClick: () =>
window.open(
"https://github.com/redplanethq/core/issues/new",
"_blank",
),
variant: "secondary",
},
]}
/>
<div className="h-[calc(100vh_-_56px)] overflow-hidden p-4 px-5">
<Section
title={integration.name}
description={integration.description}
icon={
<div className="bg-grayAlpha-100 flex h-12 w-12 items-center justify-center rounded">
<Component size={24} />
</div>
</div>
{/* Connect Section */}
{!activeAccount && (hasApiKey || hasOAuth2) && (
<div className="mt-6 space-y-4">
<h3 className="text-lg font-medium">
Connect to {integration.name}
</h3>
{/* API Key Authentication */}
<ApiKeyAuthSection
integration={integration}
specData={specData}
activeAccount={activeAccount}
/>
{/* OAuth Authentication */}
<OAuthAuthSection
integration={integration}
specData={specData}
activeAccount={activeAccount}
/>
}
>
<div>
{/* Authentication Methods */}
<div className="space-y-4">
<h3 className="text-lg font-medium">Authentication Methods</h3>
<div className="space-y-2">
{hasApiKey && (
<div className="flex items-center gap-2">
<span className="inline-flex items-center gap-2 text-sm">
<Checkbox checked /> API Key authentication
</span>
</div>
)}
{hasOAuth2 && (
<div className="flex items-center gap-2">
<span className="inline-flex items-center gap-2 text-sm">
<Checkbox checked />
OAuth 2.0 authentication
</span>
</div>
)}
{!hasApiKey && !hasOAuth2 && !hasMCPAuth && (
<div className="text-muted-foreground text-sm">
No authentication method specified
</div>
)}
</div>
</div>
)}
{/* Connected Account Info */}
<ConnectedAccountSection activeAccount={activeAccount} />
{/* Connect Section */}
{!activeAccount && (hasApiKey || hasOAuth2) && (
<div className="mt-6 space-y-4">
<h3 className="text-lg font-medium">
Connect to {integration.name}
</h3>
{/* MCP Authentication Section */}
<MCPAuthSection
integration={integration}
activeAccount={activeAccount as any}
hasMCPAuth={hasMCPAuth}
/>
{/* API Key Authentication */}
<ApiKeyAuthSection
integration={integration}
specData={specData}
activeAccount={activeAccount}
/>
{/* Ingestion Rule Section */}
<IngestionRuleSection
ingestionRule={ingestionRule}
activeAccount={activeAccount}
/>
</div>
</Section>
{/* OAuth Authentication */}
<OAuthAuthSection
integration={integration}
specData={specData}
activeAccount={activeAccount}
/>
</div>
)}
{/* Connected Account Info */}
<ConnectedAccountSection activeAccount={activeAccount} />
{/* MCP Authentication Section */}
<MCPAuthSection
integration={integration}
activeAccount={activeAccount as any}
hasMCPAuth={hasMCPAuth}
/>
{/* Ingestion Rule Section */}
<IngestionRuleSection
ingestionRule={ingestionRule}
activeAccount={activeAccount}
/>
</div>
</Section>
</div>
</div>
);
}

View File

@ -6,6 +6,8 @@ import { requireUserId, requireWorkpace } from "~/services/session.server";
import { getIntegrationDefinitions } from "~/services/integrationDefinition.server";
import { getIntegrationAccounts } from "~/services/integrationAccount.server";
import { IntegrationGrid } from "~/components/integrations/integration-grid";
import { PageHeader } from "~/components/common/page-header";
import { Plus } from "lucide-react";
export async function loader({ request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
@ -38,11 +40,28 @@ export default function Integrations() {
);
return (
<div className="home flex h-full flex-col overflow-y-auto p-4 px-5">
<IntegrationGrid
integrations={integrationDefinitions}
activeAccountIds={activeAccountIds}
<div className="flex h-full flex-col">
<PageHeader
title="Integrations"
actions={[
{
label: "Request New Integration",
icon: <Plus size={14} />,
onClick: () =>
window.open(
"https://github.com/redplanethq/core/issues/new",
"_blank",
),
variant: "secondary",
},
]}
/>
<div className="home flex h-[calc(100vh_-_56px)] flex-col overflow-y-auto p-4 px-5">
<IntegrationGrid
integrations={integrationDefinitions}
activeAccountIds={activeAccountIds}
/>
</div>
</div>
);
}

View File

@ -1,12 +1,15 @@
import { useState } from "react";
import { useNavigate } from "@remix-run/react";
import { useLogs } from "~/hooks/use-logs";
import { LogsFilters } from "~/components/logs/logs-filters";
import { VirtualLogsList } from "~/components/logs/virtual-logs-list";
import { AppContainer, PageContainer } from "~/components/layout/app-layout";
import { Card, CardContent } from "~/components/ui/card";
import { Activity } from "lucide-react";
import { PageHeader } from "~/components/common/page-header";
export default function LogsActivity() {
const navigate = useNavigate();
const [selectedSource, setSelectedSource] = useState<string | undefined>();
const [selectedStatus, setSelectedStatus] = useState<string | undefined>();
@ -36,42 +39,63 @@ export default function LogsActivity() {
}
return (
<div className="space-y-6 p-4 px-5">
<LogsFilters
availableSources={availableSources}
selectedSource={selectedSource}
selectedStatus={selectedStatus}
onSourceChange={setSelectedSource}
onStatusChange={setSelectedStatus}
<div className="flex h-full flex-col">
<PageHeader
title="Logs"
tabs={[
{
label: "All",
value: "all",
isActive: false,
onClick: () => navigate("/home/logs/all"),
},
{
label: "Activity",
value: "activity",
isActive: true,
onClick: () => navigate("/home/logs/activity"),
},
]}
/>
{/* Logs List */}
<div className="space-y-4">
{logs.length === 0 ? (
<Card>
<CardContent className="bg-background-2 flex items-center justify-center py-16">
<div className="text-center">
<Activity className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 className="mb-2 text-lg font-semibold">
No activity logs found
</h3>
<p className="text-muted-foreground">
{selectedSource || selectedStatus
? "Try adjusting your filters to see more results."
: "No activity ingestion logs are available yet."}
</p>
</div>
</CardContent>
</Card>
) : (
<VirtualLogsList
logs={logs}
hasMore={hasMore}
loadMore={loadMore}
isLoading={isLoading}
height={600}
<div className="flex h-[calc(100vh_-_56px)] flex-col space-y-6 p-4 px-5">
{logs.length > 0 && (
<LogsFilters
availableSources={availableSources}
selectedSource={selectedSource}
selectedStatus={selectedStatus}
onSourceChange={setSelectedSource}
onStatusChange={setSelectedStatus}
/>
)}
{/* Logs List */}
<div className="space-y-4">
{logs.length === 0 ? (
<Card>
<CardContent className="bg-background-2 flex items-center justify-center py-16">
<div className="text-center">
<Activity className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 className="mb-2 text-lg font-semibold">
No activity logs found
</h3>
<p className="text-muted-foreground">
{selectedSource || selectedStatus
? "Try adjusting your filters to see more results."
: "No activity ingestion logs are available yet."}
</p>
</div>
</CardContent>
</Card>
) : (
<VirtualLogsList
logs={logs}
hasMore={hasMore}
loadMore={loadMore}
isLoading={isLoading}
height={600}
/>
)}
</div>
</div>
</div>
);

View File

@ -1,12 +1,15 @@
import { useState } from "react";
import { useNavigate } from "@remix-run/react";
import { useLogs } from "~/hooks/use-logs";
import { LogsFilters } from "~/components/logs/logs-filters";
import { VirtualLogsList } from "~/components/logs/virtual-logs-list";
import { AppContainer, PageContainer } from "~/components/layout/app-layout";
import { Card, CardContent } from "~/components/ui/card";
import { Database } from "lucide-react";
import { PageHeader } from "~/components/common/page-header";
export default function LogsAll() {
const navigate = useNavigate();
const [selectedSource, setSelectedSource] = useState<string | undefined>();
const [selectedStatus, setSelectedStatus] = useState<string | undefined>();
@ -36,42 +39,63 @@ export default function LogsAll() {
}
return (
<div className="space-y-6 p-4 px-5">
{/* Filters */}
<LogsFilters
availableSources={availableSources}
selectedSource={selectedSource}
selectedStatus={selectedStatus}
onSourceChange={setSelectedSource}
onStatusChange={setSelectedStatus}
<>
<PageHeader
title="Logs"
tabs={[
{
label: "All",
value: "all",
isActive: true,
onClick: () => navigate("/home/logs/all"),
},
{
label: "Activity",
value: "activity",
isActive: false,
onClick: () => navigate("/home/logs/activity"),
},
]}
/>
{/* Logs List */}
<div className="space-y-4">
{logs.length === 0 ? (
<Card>
<CardContent className="bg-background-2 flex items-center justify-center py-16">
<div className="text-center">
<Database className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 className="mb-2 text-lg font-semibold">No logs found</h3>
<p className="text-muted-foreground">
{selectedSource || selectedStatus
? "Try adjusting your filters to see more results."
: "No ingestion logs are available yet."}
</p>
</div>
</CardContent>
</Card>
) : (
<VirtualLogsList
logs={logs}
hasMore={hasMore}
loadMore={loadMore}
isLoading={isLoading}
height={600}
<div className="space-y-6 p-4 px-5">
{/* Filters */}
{logs.length > 0 && (
<LogsFilters
availableSources={availableSources}
selectedSource={selectedSource}
selectedStatus={selectedStatus}
onSourceChange={setSelectedSource}
onStatusChange={setSelectedStatus}
/>
)}
{/* Logs List */}
<div className="space-y-4">
{logs.length === 0 ? (
<Card>
<CardContent className="bg-background-2 flex items-center justify-center py-16">
<div className="text-center">
<Database className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 className="mb-2 text-lg font-semibold">No logs found</h3>
<p className="text-muted-foreground">
{selectedSource || selectedStatus
? "Try adjusting your filters to see more results."
: "No ingestion logs are available yet."}
</p>
</div>
</CardContent>
</Card>
) : (
<VirtualLogsList
logs={logs}
hasMore={hasMore}
loadMore={loadMore}
isLoading={isLoading}
height={600}
/>
)}
</div>
</div>
</div>
</>
);
}

View File

@ -7,7 +7,6 @@ import { clearRedirectTo, commitSession } from "~/services/redirectTo.server";
import { AppSidebar } from "~/components/sidebar/app-sidebar";
import { SidebarInset, SidebarProvider } from "~/components/ui/sidebar";
import { SiteHeader } from "~/components/ui/header";
import { FloatingIngestionStatus } from "~/components/ingestion/floating-ingestion-status";
export const loader = async ({ request }: LoaderFunctionArgs) => {
@ -40,8 +39,7 @@ export default function Home() {
>
<AppSidebar variant="inset" />
<SidebarInset className="bg-background-2 h-full rounded pr-0">
<SiteHeader />
<div className="flex h-[calc(100vh_-_56px)] flex-col rounded">
<div className="flex h-full flex-col rounded">
<div className="@container/main flex h-full flex-col gap-2">
<div className="flex h-full flex-col">
<Outlet />