Merge remote-tracking branch 'upstream/dev' into fix/auth

This commit is contained in:
Anish Sarkar 2026-02-10 11:36:06 +05:30
commit 2dec643cb4
80 changed files with 2968 additions and 2379 deletions

View file

@ -16,7 +16,6 @@ import { trackLoginAttempt, trackLoginFailure, trackLoginSuccess } from "@/lib/p
export function LocalLoginForm() {
const t = useTranslations("auth");
const tCommon = useTranslations("common");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
@ -58,12 +57,6 @@ export function LocalLoginForm() {
sessionStorage.setItem("login_success_tracked", "true");
}
// Success toast
toast.success(t("login_success"), {
description: "Redirecting to dashboard",
duration: 2000,
});
// Small delay to show success message
setTimeout(() => {
router.push(`/auth/callback?token=${data.access_token}`);
@ -103,7 +96,7 @@ export function LocalLoginForm() {
<form onSubmit={handleSubmit} className="space-y-3 md:space-y-4">
{/* Error Display */}
<AnimatePresence>
{error && error.title && (
{error?.title && (
<motion.div
initial={{ opacity: 0, y: -10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}

View file

@ -1,19 +1,51 @@
"use client";
import type React from "react";
import { useRef, useState, useEffect } from "react";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useEffect, useRef, useState } from "react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
export function getDocumentTypeIcon(type: string, className?: string): React.ReactNode {
return getConnectorIcon(type, className);
}
export function getDocumentTypeLabel(type: string): string {
return type
.split("_")
.map((word) => word.charAt(0) + word.slice(1).toLowerCase())
.join(" ");
const labelMap: Record<string, string> = {
EXTENSION: "Extension",
CRAWLED_URL: "Web Page",
FILE: "File",
SLACK_CONNECTOR: "Slack",
TEAMS_CONNECTOR: "Microsoft Teams",
NOTION_CONNECTOR: "Notion",
YOUTUBE_VIDEO: "YouTube Video",
GITHUB_CONNECTOR: "GitHub",
LINEAR_CONNECTOR: "Linear",
DISCORD_CONNECTOR: "Discord",
JIRA_CONNECTOR: "Jira",
CONFLUENCE_CONNECTOR: "Confluence",
CLICKUP_CONNECTOR: "ClickUp",
GOOGLE_CALENDAR_CONNECTOR: "Google Calendar",
GOOGLE_GMAIL_CONNECTOR: "Gmail",
GOOGLE_DRIVE_FILE: "Google Drive",
AIRTABLE_CONNECTOR: "Airtable",
LUMA_CONNECTOR: "Luma",
ELASTICSEARCH_CONNECTOR: "Elasticsearch",
BOOKSTACK_CONNECTOR: "BookStack",
CIRCLEBACK: "Circleback",
OBSIDIAN_CONNECTOR: "Obsidian",
SURFSENSE_DOCS: "SurfSense Docs",
NOTE: "Note",
COMPOSIO_GOOGLE_DRIVE_CONNECTOR: "Composio Google Drive",
COMPOSIO_GMAIL_CONNECTOR: "Composio Gmail",
COMPOSIO_GOOGLE_CALENDAR_CONNECTOR: "Composio Google Calendar",
};
return (
labelMap[type] ||
type
.split("_")
.map((word) => word.charAt(0) + word.slice(1).toLowerCase())
.join(" ")
);
}
export function DocumentTypeChip({ type, className }: { type: string; className?: string }) {

View file

@ -11,14 +11,13 @@ import {
Clock,
FileText,
FileX,
Loader2,
Network,
Plus,
User,
} from "lucide-react";
import { motion } from "motion/react";
import { useTranslations } from "next-intl";
import React, { useRef, useState, useEffect, useCallback } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { useDocumentUploadDialog } from "@/components/assistant-ui/document-upload-popup";
import { JsonMetadataViewer } from "@/components/json-metadata-viewer";
import { MarkdownViewer } from "@/components/markdown-viewer";
@ -354,11 +353,11 @@ export function DocumentsTableShell({
<Skeleton className="h-4 w-4 rounded" />
</div>
</TableHead>
<TableHead className="w-[35%] max-w-0 border-r border-border/40">
<TableHead className="w-[40%] max-w-0 border-r border-border/40">
<Skeleton className="h-3 w-20" />
</TableHead>
{columnVisibility.document_type && (
<TableHead className="w-[20%] min-w-[120px] max-w-[200px] border-r border-border/40">
<TableHead className="w-[15%] min-w-[100px] max-w-[170px] border-r border-border/40">
<Skeleton className="h-3 w-14" />
</TableHead>
)}
@ -396,11 +395,11 @@ export function DocumentsTableShell({
<Skeleton className="h-4 w-4 rounded" />
</div>
</TableCell>
<TableCell className="w-[35%] py-2.5 max-w-0 border-r border-border/40">
<TableCell className="w-[40%] py-2.5 max-w-0 border-r border-border/40">
<Skeleton className="h-4" style={{ width: `${widthPercent}%` }} />
</TableCell>
{columnVisibility.document_type && (
<TableCell className="w-[20%] min-w-[120px] max-w-[200px] py-2.5 border-r border-border/40 overflow-hidden">
<TableCell className="w-[15%] min-w-[100px] max-w-[170px] py-2.5 border-r border-border/40 overflow-hidden">
<Skeleton className="h-5 w-24 rounded" />
</TableCell>
)}
@ -499,7 +498,7 @@ export function DocumentsTableShell({
/>
</div>
</TableHead>
<TableHead className="w-[35%] border-r border-border/40">
<TableHead className="w-[40%] border-r border-border/40">
<SortableHeader
sortKey="title"
currentSortKey={sortKey}
@ -511,7 +510,7 @@ export function DocumentsTableShell({
</SortableHeader>
</TableHead>
{columnVisibility.document_type && (
<TableHead className="w-[20%] min-w-[120px] max-w-[200px] border-r border-border/40">
<TableHead className="w-[15%] min-w-[100px] max-w-[170px] border-r border-border/40">
<SortableHeader
sortKey="document_type"
currentSortKey={sortKey}
@ -594,7 +593,7 @@ export function DocumentsTableShell({
/>
</div>
</TableCell>
<TableCell className="w-[35%] py-2.5 max-w-0 border-r border-border/40">
<TableCell className="w-[40%] py-2.5 max-w-0 border-r border-border/40">
<button
type="button"
className="block w-full text-left text-sm text-foreground hover:text-foreground transition-colors cursor-pointer bg-transparent border-0 p-0 truncate"
@ -624,7 +623,7 @@ export function DocumentsTableShell({
</button>
</TableCell>
{columnVisibility.document_type && (
<TableCell className="w-[20%] min-w-[120px] max-w-[200px] py-2.5 border-r border-border/40 overflow-hidden">
<TableCell className="w-[15%] min-w-[100px] max-w-[170px] py-2.5 border-r border-border/40 overflow-hidden">
<DocumentTypeChip type={doc.document_type} />
</TableCell>
)}
@ -773,7 +772,7 @@ export function DocumentsTableShell({
<div className="mt-4">
{viewingLoading ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
<Spinner size="lg" className="text-muted-foreground" />
</div>
) : (
<MarkdownViewer content={viewingContent} />

View file

@ -50,13 +50,16 @@ export function RowActions({
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 and Delete are disabled while processing
const isEditDisabled = isBeingProcessed;
// Edit is disabled while processing OR for failed FILE documents
const isEditDisabled = isBeingProcessed || isFileFailed;
const isDeleteDisabled = isBeingProcessed;
const handleDelete = async () => {

View file

@ -2,7 +2,7 @@
import { IconCalendar, IconMailFilled } from "@tabler/icons-react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Check, ExternalLink, Gift, Loader2, Mail, Star } from "lucide-react";
import { Check, ExternalLink, Gift, Mail, Star } from "lucide-react";
import { motion } from "motion/react";
import Link from "next/link";
import { useEffect } from "react";
@ -19,6 +19,7 @@ import {
} from "@/components/ui/dialog";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Spinner } from "@/components/ui/spinner";
import type { IncentiveTaskInfo } from "@/contracts/types/incentive-tasks.types";
import { incentiveTasksApiService } from "@/lib/apis/incentive-tasks-api.service";
import {
@ -144,7 +145,7 @@ export default function MorePagesPage() {
className="gap-1"
>
{completeMutation.isPending ? (
<Loader2 className="h-3 w-3 animate-spin" />
<Spinner size="xs" />
) : (
<>
Go

View file

@ -44,7 +44,6 @@ import { useMessagesElectric } from "@/hooks/use-messages-electric";
import { documentsApiService } from "@/lib/apis/documents-api.service";
// import { WriteTodosToolUI } from "@/components/tool-ui/write-todos";
import { getBearerToken } from "@/lib/auth-utils";
import { createAttachmentAdapter, extractAttachmentContent } from "@/lib/chat/attachment-adapter";
import { convertToThreadMessage } from "@/lib/chat/message-utils";
import {
isPodcastGenerating,
@ -216,9 +215,6 @@ export default function NewChatPage() {
useMessagesElectric(threadId, handleElectricMessagesUpdate);
// Create the attachment adapter for file processing
const attachmentAdapter = useMemo(() => createAttachmentAdapter(), []);
// Extract search_space_id from URL params
const searchSpaceId = useMemo(() => {
const id = params.search_space_id;
@ -409,16 +405,7 @@ export default function NewChatPage() {
}
}
// Extract attachments from message
// AppendMessage.attachments contains the processed attachment objects (from adapter.send())
const messageAttachments: Array<Record<string, unknown>> = [];
if (message.attachments && message.attachments.length > 0) {
for (const att of message.attachments) {
messageAttachments.push(att as unknown as Record<string, unknown>);
}
}
if (!userQuery.trim() && messageAttachments.length === 0) return;
if (!userQuery.trim()) return;
// Check if podcast is already generating
if (isPodcastGenerating() && looksLikePodcastRequest(userQuery)) {
@ -485,14 +472,13 @@ export default function NewChatPage() {
role: "user",
content: message.content,
createdAt: new Date(),
attachments: message.attachments || [],
metadata: authorMetadata,
};
setMessages((prev) => [...prev, userMessage]);
// Track message sent
trackChatMessageSent(searchSpaceId, currentThreadId, {
hasAttachments: messageAttachments.length > 0,
hasAttachments: false,
hasMentionedDocuments:
mentionedDocumentIds.surfsense_doc_ids.length > 0 ||
mentionedDocumentIds.document_ids.length > 0,
@ -512,7 +498,7 @@ export default function NewChatPage() {
}));
}
// Persist user message with mentioned documents and attachments (don't await, fire and forget)
// Persist user message with mentioned documents (don't await, fire and forget)
const persistContent: unknown[] = [...message.content];
// Add mentioned documents for persistence
@ -527,23 +513,6 @@ export default function NewChatPage() {
});
}
// Add attachments for persistence (so they survive page reload)
if (message.attachments && message.attachments.length > 0) {
persistContent.push({
type: "attachments",
items: message.attachments.map((att) => ({
id: att.id,
name: att.name,
type: att.type,
contentType: (att as { contentType?: string }).contentType,
// Include imageDataUrl for images so they can be displayed after reload
imageDataUrl: (att as { imageDataUrl?: string }).imageDataUrl,
// Include extractedContent for context (already extracted, no re-processing needed)
extractedContent: (att as { extractedContent?: string }).extractedContent,
})),
});
}
appendMessage(currentThreadId, {
role: "user",
content: persistContent,
@ -688,9 +657,6 @@ export default function NewChatPage() {
})
.filter((m) => m.content.length > 0);
// Extract attachment content to send with the request
const attachments = extractAttachmentContent(messageAttachments);
// Get mentioned document IDs for context (separate fields for backend)
const hasDocumentIds = mentionedDocumentIds.document_ids.length > 0;
const hasSurfsenseDocIds = mentionedDocumentIds.surfsense_doc_ids.length > 0;
@ -715,7 +681,6 @@ export default function NewChatPage() {
user_query: userQuery.trim(),
search_space_id: searchSpaceId,
messages: messageHistory,
attachments: attachments.length > 0 ? attachments : undefined,
mentioned_document_ids: hasDocumentIds ? mentionedDocumentIds.document_ids : undefined,
mentioned_surfsense_doc_ids: hasSurfsenseDocIds
? mentionedDocumentIds.surfsense_doc_ids
@ -1010,7 +975,6 @@ export default function NewChatPage() {
// Extract the original user query BEFORE removing messages (for reload mode)
let userQueryToDisplay = newUserQuery;
let originalUserMessageContent: ThreadMessageLike["content"] | null = null;
let originalUserMessageAttachments: ThreadMessageLike["attachments"] | undefined;
let originalUserMessageMetadata: ThreadMessageLike["metadata"] | undefined;
if (!newUserQuery) {
@ -1018,7 +982,6 @@ export default function NewChatPage() {
const lastUserMessage = [...messages].reverse().find((m) => m.role === "user");
if (lastUserMessage) {
originalUserMessageContent = lastUserMessage.content;
originalUserMessageAttachments = lastUserMessage.attachments;
originalUserMessageMetadata = lastUserMessage.metadata;
// Extract text for the API request
for (const part of lastUserMessage.content) {
@ -1144,7 +1107,6 @@ export default function NewChatPage() {
? [{ type: "text", text: newUserQuery }]
: originalUserMessageContent || [{ type: "text", text: userQueryToDisplay || "" }],
createdAt: new Date(),
attachments: newUserQuery ? undefined : originalUserMessageAttachments,
metadata: newUserQuery ? undefined : originalUserMessageMetadata,
};
setMessages((prev) => [...prev, userMessage]);
@ -1391,7 +1353,7 @@ export default function NewChatPage() {
await handleRegenerate(null);
}, [handleRegenerate]);
// Create external store runtime with attachment support
// Create external store runtime
const runtime = useExternalStoreRuntime({
messages,
isRunning,
@ -1400,9 +1362,6 @@ export default function NewChatPage() {
onReload,
convertMessage,
onCancel: cancelRun,
adapters: {
attachments: attachmentAdapter,
},
});
// Show loading state only when loading an existing thread