Merge remote-tracking branch 'upstream/dev' into refactor/indexing-pipelines

This commit is contained in:
Anish Sarkar 2026-03-27 22:36:34 +05:30
commit 17091edb77
104 changed files with 4944 additions and 1319 deletions

View file

@ -63,7 +63,7 @@ export function DocumentTypeChip({ type, className }: { type: string; className?
checkTruncation();
window.addEventListener("resize", checkTruncation);
return () => window.removeEventListener("resize", checkTruncation);
}, []);
}, [type]);
const chip = (
<span

View file

@ -1,6 +1,6 @@
"use client";
import { ListFilter, Search, Upload, X } from "lucide-react";
import { FolderPlus, ListFilter, Search, Upload, X } from "lucide-react";
import { useTranslations } from "next-intl";
import React, { useCallback, useMemo, useRef, useState } from "react";
import { useDocumentUploadDialog } from "@/components/assistant-ui/document-upload-popup";
@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
import { getDocumentTypeIcon, getDocumentTypeLabel } from "./DocumentTypeIcon";
@ -17,12 +18,14 @@ export function DocumentsFilters({
searchValue,
onToggleType,
activeTypes,
onCreateFolder,
}: {
typeCounts: Partial<Record<DocumentTypeEnum, number>>;
onSearch: (v: string) => void;
searchValue: string;
onToggleType: (type: DocumentTypeEnum, checked: boolean) => void;
activeTypes: DocumentTypeEnum[];
onCreateFolder?: () => void;
}) {
const t = useTranslations("documents");
const id = React.useId();
@ -194,6 +197,23 @@ export function DocumentsFilters({
)}
</div>
{/* New Folder Button */}
{onCreateFolder && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
className="h-9 w-9 shrink-0 border-dashed border-sidebar-border text-sidebar-foreground/60 hover:text-sidebar-foreground hover:border-sidebar-border bg-sidebar"
onClick={onCreateFolder}
>
<FolderPlus size={14} />
</Button>
</TooltipTrigger>
<TooltipContent>New folder</TooltipContent>
</Tooltip>
)}
{/* Upload Button */}
<Button
data-joyride="upload-button"

View file

@ -24,8 +24,7 @@ import {
} from "@/components/ui/dropdown-menu";
import type { Document } from "./types";
// Only FILE and NOTE document types can be edited
const EDITABLE_DOCUMENT_TYPES = ["FILE", "NOTE"] as const;
const EDITABLE_DOCUMENT_TYPES = ["NOTE"] as const;
// SURFSENSE_DOCS are system-managed and cannot be deleted
const NON_DELETABLE_DOCUMENT_TYPES = ["SURFSENSE_DOCS"] as const;
@ -47,20 +46,14 @@ export function RowActions({
document.document_type as (typeof EDITABLE_DOCUMENT_TYPES)[number]
);
// Documents in "pending" or "processing" state should show disabled delete
const isBeingProcessed =
document.status?.state === "pending" || document.status?.state === "processing";
// FILE documents that failed processing cannot be edited
const isFileFailed = document.document_type === "FILE" && document.status?.state === "failed";
// SURFSENSE_DOCS are system-managed and should not show delete at all
const shouldShowDelete = !NON_DELETABLE_DOCUMENT_TYPES.includes(
document.document_type as (typeof NON_DELETABLE_DOCUMENT_TYPES)[number]
);
// Edit is disabled while processing OR for failed FILE documents
const isEditDisabled = isBeingProcessed || isFileFailed;
const isEditDisabled = isBeingProcessed;
const isDeleteDisabled = isBeingProcessed;
const handleDelete = async () => {

View file

@ -0,0 +1,133 @@
"use client";
import { motion } from "motion/react";
import { Skeleton } from "@/components/ui/skeleton";
export default function Loading() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className="w-full px-6 py-4 space-y-6 min-h-[calc(100vh-64px)]"
>
{/* Summary Dashboard Skeleton */}
<motion.div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
{[...Array(4)].map((_, i) => (
<div key={i} className="rounded-lg border p-4">
<div className="flex flex-row items-center justify-between space-y-0 pb-2">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-4 w-4 rounded-full" />
</div>
<div className="space-y-2">
<Skeleton className="h-8 w-16" />
<Skeleton className="h-3 w-32" />
</div>
</div>
))}
</motion.div>
{/* Header Section Skeleton */}
<motion.div
className="flex items-center justify-between"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
>
<div className="space-y-2">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-4 w-64" />
</div>
<Skeleton className="h-9 w-24" />
</motion.div>
{/* Filters Skeleton */}
<motion.div
className="flex flex-wrap items-center justify-start gap-3 w-full"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
<div className="flex items-center gap-3 flex-wrap w-full sm:w-auto">
<Skeleton className="h-9 w-full sm:w-60" />
<Skeleton className="h-9 w-24" />
<Skeleton className="h-9 w-24" />
<Skeleton className="h-9 w-20" />
</div>
</motion.div>
{/* Table Skeleton */}
<motion.div
className="rounded-md border overflow-hidden"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
{/* Table Header */}
<div className="border-b bg-muted/50 px-4 py-3 flex items-center gap-4">
<Skeleton className="h-4 w-4" />
<Skeleton className="h-4 w-16" />
<Skeleton className="h-4 w-20" />
<Skeleton className="h-4 w-24" />
<Skeleton className="h-4 flex-1" />
<Skeleton className="h-4 w-24" />
<Skeleton className="h-4 w-8" />
</div>
{/* Table Rows */}
{[...Array(6)].map((_, i) => (
<div key={i} className="border-b px-4 py-3 flex items-center gap-4 hover:bg-muted/50">
<Skeleton className="h-4 w-4" />
<Skeleton className="h-6 w-12 rounded-full" />
<Skeleton className="h-6 w-16 rounded-full" />
<div className="flex items-center gap-2">
<Skeleton className="h-4 w-4" />
<Skeleton className="h-4 w-20" />
</div>
<div className="flex-1 space-y-1">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-3 w-48" />
</div>
<div className="space-y-1">
<Skeleton className="h-3 w-24" />
<Skeleton className="h-3 w-20" />
</div>
<Skeleton className="h-8 w-8" />
</div>
))}
</motion.div>
{/* Pagination Skeleton */}
<div className="flex items-center justify-between gap-8 mt-4">
<motion.div
className="flex items-center gap-3"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
>
<Skeleton className="h-4 w-20 max-sm:sr-only" />
<Skeleton className="h-9 w-16" />
</motion.div>
<motion.div
className="flex grow justify-end"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
>
<Skeleton className="h-4 w-40" />
</motion.div>
<div className="flex items-center gap-2">
<Skeleton className="h-9 w-9" />
<Skeleton className="h-9 w-9" />
<Skeleton className="h-9 w-9" />
<Skeleton className="h-9 w-9" />
</div>
</div>
</motion.div>
);
}

View file

@ -0,0 +1,10 @@
import { Skeleton } from "@/components/ui/skeleton";
export default function Loading() {
return (
<div className="flex flex-1 flex-col items-center justify-center gap-4 p-4">
<Skeleton className="h-4 w-64" />
<Skeleton className="h-32 w-full max-w-2xl rounded-xl" />
</div>
);
}

View file

@ -32,6 +32,7 @@ import {
import { closeReportPanelAtom } from "@/atoms/chat/report-panel.atom";
import { closeEditorPanelAtom } from "@/atoms/editor/editor-panel.atom";
import { membersAtom } from "@/atoms/members/members-query.atoms";
import { updateChatTabTitleAtom } from "@/atoms/tabs/tabs.atom";
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { ThinkingStepsDataUI } from "@/components/assistant-ui/thinking-steps";
import { Thread } from "@/components/assistant-ui/thread";
@ -74,6 +75,7 @@ import {
trackChatMessageSent,
trackChatResponseReceived,
} from "@/lib/posthog/events";
import Loading from "../loading";
/**
* After a tool produces output, mark any previously-decided interrupt tool
@ -188,6 +190,7 @@ export default function NewChatPage() {
const clearTargetCommentId = useSetAtom(clearTargetCommentIdAtom);
const closeReportPanel = useSetAtom(closeReportPanelAtom);
const closeEditorPanel = useSetAtom(closeEditorPanelAtom);
const updateChatTabTitle = useSetAtom(updateChatTabTitleAtom);
// Get current user for author info in shared chats
const { data: currentUser } = useAtomValue(currentUserAtom);
@ -726,12 +729,10 @@ export default function NewChatPage() {
}
case "data-thread-title-update": {
// Handle thread title update from LLM-generated title
const titleData = parsed.data as { threadId: number; title: string };
if (titleData?.title && titleData?.threadId === currentThreadId) {
// Update current thread state with new title
setCurrentThread((prev) => (prev ? { ...prev, title: titleData.title } : prev));
// Invalidate thread list to refresh sidebar
updateChatTabTitle({ chatId: currentThreadId, title: titleData.title });
queryClient.invalidateQueries({
queryKey: ["threads", String(searchSpaceId)],
});
@ -1526,42 +1527,7 @@ export default function NewChatPage() {
// Show loading state only when loading an existing thread
if (isInitializing) {
return (
<div className="flex h-[calc(100dvh-64px)] flex-col bg-main-panel px-4">
<div className="mx-auto w-full max-w-[44rem] flex flex-1 flex-col gap-6 py-8">
{/* User message */}
<div className="flex justify-end">
<Skeleton className="h-12 w-56 rounded-2xl" />
</div>
{/* Assistant message */}
<div className="flex flex-col gap-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-[85%]" />
<Skeleton className="h-4 w-[70%]" />
</div>
{/* User message */}
<div className="flex justify-end">
<Skeleton className="h-12 w-40 rounded-2xl" />
</div>
{/* Assistant message */}
<div className="flex flex-col gap-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-[90%]" />
<Skeleton className="h-4 w-[60%]" />
</div>
</div>
{/* Input bar */}
<div className="sticky bottom-0 pb-6 bg-main-panel">
<div className="mx-auto w-full max-w-[44rem]">
<Skeleton className="h-24 w-full rounded-2xl" />
</div>
</div>
</div>
);
return <Loading />;
}
// Show error state only if we tried to load an existing thread but failed

View file

@ -0,0 +1,45 @@
import { Skeleton } from "@/components/ui/skeleton";
export default function Loading() {
return (
<div className="flex h-[calc(100dvh-64px)] flex-col bg-main-panel px-4">
<div className="mx-auto w-full max-w-[44rem] flex flex-1 flex-col gap-6 py-8">
{/* User message */}
<div className="flex justify-end">
<Skeleton className="h-12 w-56 rounded-2xl" />
</div>
{/* Assistant message */}
<div className="flex flex-col gap-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-[85%]" />
<Skeleton className="h-18 w-[40%]" />
</div>
{/* User message */}
<div className="flex gap-2 justify-end">
<Skeleton className="h-12 w-72 rounded-2xl" />
</div>
{/* Assistant message */}
<div className="flex flex-col gap-2">
<Skeleton className="h-10 w-[30%]" />
<Skeleton className="h-4 w-[90%]" />
<Skeleton className="h-6 w-[60%]" />
</div>
{/* User message */}
<div className="flex gap-2 justify-end">
<Skeleton className="h-12 w-96 rounded-2xl" />
</div>
</div>
{/* Input bar */}
<div className="sticky bottom-0 pb-6 bg-main-panel">
<div className="mx-auto w-full max-w-[44rem]">
<Skeleton className="h-24 w-full rounded-2xl" />
</div>
</div>
</div>
);
}

View file

@ -1,15 +1,10 @@
"use client";
import { redirect } from "next/navigation";
import { useParams, useRouter } from "next/navigation";
import { useEffect } from "react";
export default function SearchSpaceDashboardPage() {
const router = useRouter();
const { search_space_id } = useParams();
useEffect(() => {
router.push(`/dashboard/${search_space_id}/new-chat`);
}, [router, search_space_id]);
return <></>;
export default async function SearchSpaceDashboardPage({
params,
}: {
params: Promise<{ search_space_id: string }>;
}) {
const { search_space_id } = await params;
redirect(`/dashboard/${search_space_id}/new-chat`);
}