chore: ran linting

This commit is contained in:
Anish Sarkar 2026-04-03 13:14:40 +05:30
parent 6ace8850bb
commit 746c730b2e
31 changed files with 801 additions and 660 deletions

View file

@ -160,11 +160,11 @@ export function LocalLoginForm() {
placeholder="you@example.com"
value={username}
onChange={(e) => setUsername(e.target.value)}
className={`mt-1 block w-full rounded-md border px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-1 bg-background text-foreground transition-all ${
error.title
? "border-destructive focus:border-destructive focus:ring-destructive/40"
: "border-border focus:border-primary focus:ring-primary/40"
}`}
className={`mt-1 block w-full rounded-md border px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-1 bg-background text-foreground transition-all ${
error.title
? "border-destructive focus:border-destructive focus:ring-destructive/40"
: "border-border focus:border-primary focus:ring-primary/40"
}`}
disabled={isLoggingIn}
/>
</div>
@ -181,11 +181,11 @@ export function LocalLoginForm() {
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className={`mt-1 block w-full rounded-md border pr-10 px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-1 bg-background text-foreground transition-all ${
error.title
? "border-destructive focus:border-destructive focus:ring-destructive/40"
: "border-border focus:border-primary focus:ring-primary/40"
}`}
className={`mt-1 block w-full rounded-md border pr-10 px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-1 bg-background text-foreground transition-all ${
error.title
? "border-destructive focus:border-destructive focus:ring-destructive/40"
: "border-border focus:border-primary focus:ring-primary/40"
}`}
disabled={isLoggingIn}
/>
<button

View file

@ -229,72 +229,66 @@ export default function RegisterPage() {
</AnimatePresence>
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-foreground"
>
{t("email")}
</label>
<input
id="email"
type="email"
required
placeholder="you@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
className={`mt-1 block w-full rounded-md border px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-1 bg-background text-foreground transition-all ${
error.title
? "border-destructive focus:border-destructive focus:ring-destructive/40"
: "border-border focus:border-primary focus:ring-primary/40"
}`}
disabled={isRegistering}
/>
</div>
<label htmlFor="email" className="block text-sm font-medium text-foreground">
{t("email")}
</label>
<input
id="email"
type="email"
required
placeholder="you@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
className={`mt-1 block w-full rounded-md border px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-1 bg-background text-foreground transition-all ${
error.title
? "border-destructive focus:border-destructive focus:ring-destructive/40"
: "border-border focus:border-primary focus:ring-primary/40"
}`}
disabled={isRegistering}
/>
</div>
<div>
<label
htmlFor="password"
className="block text-sm font-medium text-foreground"
>
{t("password")}
</label>
<input
id="password"
type="password"
required
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className={`mt-1 block w-full rounded-md border px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-1 bg-background text-foreground transition-all ${
error.title
? "border-destructive focus:border-destructive focus:ring-destructive/40"
: "border-border focus:border-primary focus:ring-primary/40"
}`}
disabled={isRegistering}
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-foreground">
{t("password")}
</label>
<input
id="password"
type="password"
required
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className={`mt-1 block w-full rounded-md border px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-1 bg-background text-foreground transition-all ${
error.title
? "border-destructive focus:border-destructive focus:ring-destructive/40"
: "border-border focus:border-primary focus:ring-primary/40"
}`}
disabled={isRegistering}
/>
</div>
<div>
<label
htmlFor="confirmPassword"
className="block text-sm font-medium text-foreground"
>
{t("confirm_password")}
</label>
<input
id="confirmPassword"
type="password"
required
placeholder="Confirm your password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className={`mt-1 block w-full rounded-md border px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-1 bg-background text-foreground transition-all ${
error.title
? "border-destructive focus:border-destructive focus:ring-destructive/40"
: "border-border focus:border-primary focus:ring-primary/40"
}`}
disabled={isRegistering}
/>
<div>
<label
htmlFor="confirmPassword"
className="block text-sm font-medium text-foreground"
>
{t("confirm_password")}
</label>
<input
id="confirmPassword"
type="password"
required
placeholder="Confirm your password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className={`mt-1 block w-full rounded-md border px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-1 bg-background text-foreground transition-all ${
error.title
? "border-destructive focus:border-destructive focus:ring-destructive/40"
: "border-border focus:border-primary focus:ring-primary/40"
}`}
disabled={isRegistering}
/>
</div>
<button
@ -312,12 +306,9 @@ export default function RegisterPage() {
</form>
<div className="mt-4 text-center text-sm">
<p className="text-muted-foreground">
{t("already_have_account")}{" "}
<Link
href="/login"
className="font-medium text-primary hover:text-primary/90"
>
<p className="text-muted-foreground">
{t("already_have_account")}{" "}
<Link href="/login" className="font-medium text-primary hover:text-primary/90">
{t("sign_in")}
</Link>
</p>

View file

@ -214,17 +214,17 @@ export function DocumentsFilters({
</Tooltip>
)}
{/* Upload Button */}
<Button
data-joyride="upload-button"
onClick={openUploadDialog}
variant="outline"
size="sm"
className="h-9 shrink-0 gap-1.5 bg-white text-gray-700 border-white hover:bg-gray-50 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100"
>
<Upload size={14} />
<span>Upload</span>
</Button>
{/* Upload Button */}
<Button
data-joyride="upload-button"
onClick={openUploadDialog}
variant="outline"
size="sm"
className="h-9 shrink-0 gap-1.5 bg-white text-gray-700 border-white hover:bg-gray-50 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100"
>
<Upload size={14} />
<span>Upload</span>
</Button>
</div>
</div>
);

View file

@ -2,7 +2,6 @@
import { useAtomValue } from "jotai";
import { AlertTriangle, Globe, Lock, PenLine, Sparkles, Trash2 } from "lucide-react";
import { ShortcutKbd } from "@/components/ui/shortcut-kbd";
import { useCallback, useState } from "react";
import { toast } from "sonner";
import {
@ -24,6 +23,7 @@ import {
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { ShortcutKbd } from "@/components/ui/shortcut-kbd";
import { Spinner } from "@/components/ui/spinner";
import { Switch } from "@/components/ui/switch";
import type { PromptRead } from "@/contracts/types/prompts.types";
@ -145,9 +145,8 @@ export function PromptsContent() {
<div className="space-y-6 min-w-0 overflow-hidden">
<div className="flex items-center justify-between">
<p className="text-sm text-muted-foreground">
Create prompt templates triggered with{" "}
<ShortcutKbd keys={["/"]} className="ml-0" /> in the
chat composer.
Create prompt templates triggered with <ShortcutKbd keys={["/"]} className="ml-0" /> in
the chat composer.
</p>
{!showForm && (
<Button

View file

@ -374,7 +374,10 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
<div className="px-4 sm:px-12 py-4 sm:py-8 pb-12 sm:pb-16">
{/* LLM Configuration Warning */}
{!llmConfigLoading && !hasDocumentSummaryLLM && (
<Alert variant="destructive" className="mb-6 bg-muted/50 rounded-xl border-destructive/30">
<Alert
variant="destructive"
className="mb-6 bg-muted/50 rounded-xl border-destructive/30"
>
<AlertTriangle className="h-4 w-4" />
<AlertTitle>LLM Configuration Required</AlertTitle>
<AlertDescription className="mt-2">

View file

@ -294,36 +294,36 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
)}
{(() => {
const isGoogleDrive = connector.connector_type === "GOOGLE_DRIVE_CONNECTOR";
const isComposioGoogleDrive =
connector.connector_type === "COMPOSIO_GOOGLE_DRIVE_CONNECTOR";
const requiresFolderSelection = isGoogleDrive || isComposioGoogleDrive;
const selectedFolders =
(connector.config?.selected_folders as
| Array<{ id: string; name: string }>
| undefined) || [];
const selectedFiles =
(connector.config?.selected_files as
| Array<{ id: string; name: string }>
| undefined) || [];
const hasItemsSelected = selectedFolders.length > 0 || selectedFiles.length > 0;
const isDisabled = requiresFolderSelection && !hasItemsSelected;
const isGoogleDrive = connector.connector_type === "GOOGLE_DRIVE_CONNECTOR";
const isComposioGoogleDrive =
connector.connector_type === "COMPOSIO_GOOGLE_DRIVE_CONNECTOR";
const requiresFolderSelection = isGoogleDrive || isComposioGoogleDrive;
const selectedFolders =
(connector.config?.selected_folders as
| Array<{ id: string; name: string }>
| undefined) || [];
const selectedFiles =
(connector.config?.selected_files as
| Array<{ id: string; name: string }>
| undefined) || [];
const hasItemsSelected = selectedFolders.length > 0 || selectedFiles.length > 0;
const isDisabled = requiresFolderSelection && !hasItemsSelected;
return (
<PeriodicSyncConfig
enabled={periodicEnabled}
frequencyMinutes={frequencyMinutes}
onEnabledChange={onPeriodicEnabledChange}
onFrequencyChange={onFrequencyChange}
disabled={isDisabled}
disabledMessage={
isDisabled
? "Select at least one folder or file above to enable periodic sync"
: undefined
}
/>
);
})()}
return (
<PeriodicSyncConfig
enabled={periodicEnabled}
frequencyMinutes={frequencyMinutes}
onEnabledChange={onPeriodicEnabledChange}
onFrequencyChange={onFrequencyChange}
disabled={isDisabled}
disabledMessage={
isDisabled
? "Select at least one folder or file above to enable periodic sync"
: undefined
}
/>
);
})()}
</>
)}

View file

@ -143,7 +143,10 @@ const DocumentUploadPopupContent: FC<{
<div className="px-4 sm:px-6 pb-4 sm:pb-6">
{!isLoading && !hasDocumentSummaryLLM ? (
<Alert variant="destructive" className="mb-4 bg-muted/50 rounded-xl border-destructive/30">
<Alert
variant="destructive"
className="mb-4 bg-muted/50 rounded-xl border-destructive/30"
>
<AlertTriangle className="h-4 w-4" />
<AlertTitle>LLM Configuration Required</AlertTitle>
<AlertDescription className="mt-2">

View file

@ -32,7 +32,8 @@ export const InlineCitation: FC<InlineCitationProps> = ({ chunkId, isDocsChunk =
<button
type="button"
onClick={() => setIsOpen(true)}
className="ml-0.5 inline-flex h-5 min-w-5 cursor-pointer items-center justify-center rounded-md bg-muted/60 px-1.5 text-[11px] font-medium text-muted-foreground align-baseline shadow-sm transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none" title={`View source chunk #${chunkId}`}
className="ml-0.5 inline-flex h-5 min-w-5 cursor-pointer items-center justify-center rounded-md bg-muted/60 px-1.5 text-[11px] font-medium text-muted-foreground align-baseline shadow-sm transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none"
title={`View source chunk #${chunkId}`}
>
{chunkId}
</button>

View file

@ -39,8 +39,8 @@ import { Spinner } from "@/components/ui/spinner";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
import { cn } from "@/lib/utils";
import { isVersionableType } from "./version-history";
import { DND_TYPES } from "./FolderNode";
import { isVersionableType } from "./version-history";
const EDITABLE_DOCUMENT_TYPES = new Set(["FILE", "NOTE"]);
@ -199,7 +199,10 @@ export const DocumentNode = React.memo(function DocumentNode({
<span className="flex-1 min-w-0 truncate">{doc.title}</span>
{getDocumentTypeIcon(doc.document_type as DocumentTypeEnum, "h-3.5 w-3.5 text-muted-foreground") && (
{getDocumentTypeIcon(
doc.document_type as DocumentTypeEnum,
"h-3.5 w-3.5 text-muted-foreground"
) && (
<span className="shrink-0">
{getDocumentTypeIcon(
doc.document_type as DocumentTypeEnum,
@ -251,10 +254,7 @@ export const DocumentNode = React.memo(function DocumentNode({
</DropdownMenuSub>
)}
{onVersionHistory && isVersionableType(doc.document_type) && (
<DropdownMenuItem
disabled={isProcessing}
onClick={() => onVersionHistory(doc)}
>
<DropdownMenuItem disabled={isProcessing} onClick={() => onVersionHistory(doc)}>
<History className="mr-2 h-4 w-4" />
Versions
</DropdownMenuItem>
@ -300,10 +300,7 @@ export const DocumentNode = React.memo(function DocumentNode({
</ContextMenuSub>
)}
{onVersionHistory && isVersionableType(doc.document_type) && (
<ContextMenuItem
disabled={isProcessing}
onClick={() => onVersionHistory(doc)}
>
<ContextMenuItem disabled={isProcessing} onClick={() => onVersionHistory(doc)}>
<History className="mr-2 h-4 w-4" />
Versions
</ContextMenuItem>

View file

@ -256,15 +256,15 @@ export const FolderNode = React.memo(function FolderNode({
isOver && !canDrop && "cursor-not-allowed"
)}
style={{ paddingLeft: `${depth * 16 + 4}px` }}
onClick={() => {
onToggleExpand(folder.id);
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onClick={() => {
onToggleExpand(folder.id);
}
}}
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onToggleExpand(folder.id);
}
}}
onDoubleClick={(e) => {
e.stopPropagation();
startRename();
@ -306,7 +306,11 @@ export const FolderNode = React.memo(function FolderNode({
) : (
<Checkbox
checked={
selectionState === "all" ? true : selectionState === "some" ? "indeterminate" : false
selectionState === "all"
? true
: selectionState === "some"
? "indeterminate"
: false
}
onCheckedChange={handleCheckChange}
onClick={(e) => e.stopPropagation()}
@ -350,107 +354,107 @@ export const FolderNode = React.memo(function FolderNode({
<MoreHorizontal className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
{isWatched && onRescan && (
<DropdownMenuContent align="end" className="w-40">
{isWatched && onRescan && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onRescan(folder);
}}
>
<RefreshCw className="mr-2 h-4 w-4" />
Re-scan
</DropdownMenuItem>
)}
{isWatched && onStopWatching && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onStopWatching(folder);
}}
>
<EyeOff className="mr-2 h-4 w-4" />
Stop watching
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onRescan(folder);
onCreateSubfolder(folder.id);
}}
>
<RefreshCw className="mr-2 h-4 w-4" />
Re-scan
<FolderPlus className="mr-2 h-4 w-4" />
New subfolder
</DropdownMenuItem>
)}
{isWatched && onStopWatching && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onStopWatching(folder);
startRename();
}}
>
<EyeOff className="mr-2 h-4 w-4" />
Stop watching
<PenLine className="mr-2 h-4 w-4" />
Rename
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onCreateSubfolder(folder.id);
}}
>
<FolderPlus className="mr-2 h-4 w-4" />
New subfolder
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
startRename();
}}
>
<PenLine className="mr-2 h-4 w-4" />
Rename
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onMove(folder);
}}
>
<Move className="mr-2 h-4 w-4" />
Move to...
</DropdownMenuItem>
<DropdownMenuItem
className="text-destructive focus:text-destructive"
onClick={(e) => {
e.stopPropagation();
onDelete(folder);
}}
>
<Trash2 className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onMove(folder);
}}
>
<Move className="mr-2 h-4 w-4" />
Move to...
</DropdownMenuItem>
<DropdownMenuItem
className="text-destructive focus:text-destructive"
onClick={(e) => {
e.stopPropagation();
onDelete(folder);
}}
>
<Trash2 className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</ContextMenuTrigger>
{!isRenaming && contextMenuOpen && (
<ContextMenuContent className="w-40">
{isWatched && onRescan && (
<ContextMenuItem onClick={() => onRescan(folder)}>
<RefreshCw className="mr-2 h-4 w-4" />
Re-scan
{!isRenaming && contextMenuOpen && (
<ContextMenuContent className="w-40">
{isWatched && onRescan && (
<ContextMenuItem onClick={() => onRescan(folder)}>
<RefreshCw className="mr-2 h-4 w-4" />
Re-scan
</ContextMenuItem>
)}
{isWatched && onStopWatching && (
<ContextMenuItem onClick={() => onStopWatching(folder)}>
<EyeOff className="mr-2 h-4 w-4" />
Stop watching
</ContextMenuItem>
)}
<ContextMenuItem onClick={() => onCreateSubfolder(folder.id)}>
<FolderPlus className="mr-2 h-4 w-4" />
New subfolder
</ContextMenuItem>
)}
{isWatched && onStopWatching && (
<ContextMenuItem onClick={() => onStopWatching(folder)}>
<EyeOff className="mr-2 h-4 w-4" />
Stop watching
<ContextMenuItem onClick={() => startRename()}>
<PenLine className="mr-2 h-4 w-4" />
Rename
</ContextMenuItem>
)}
<ContextMenuItem onClick={() => onCreateSubfolder(folder.id)}>
<FolderPlus className="mr-2 h-4 w-4" />
New subfolder
</ContextMenuItem>
<ContextMenuItem onClick={() => startRename()}>
<PenLine className="mr-2 h-4 w-4" />
Rename
</ContextMenuItem>
<ContextMenuItem onClick={() => onMove(folder)}>
<Move className="mr-2 h-4 w-4" />
Move to...
</ContextMenuItem>
<ContextMenuItem
className="text-destructive focus:text-destructive"
onClick={() => onDelete(folder)}
>
<Trash2 className="mr-2 h-4 w-4" />
Delete
</ContextMenuItem>
</ContextMenuContent>
)}
<ContextMenuItem onClick={() => onMove(folder)}>
<Move className="mr-2 h-4 w-4" />
Move to...
</ContextMenuItem>
<ContextMenuItem
className="text-destructive focus:text-destructive"
onClick={() => onDelete(folder)}
>
<Trash2 className="mr-2 h-4 w-4" />
Delete
</ContextMenuItem>
</ContextMenuContent>
)}
</ContextMenu>
);
});

View file

@ -242,10 +242,10 @@ export function FolderTreeView({
siblingPositions={siblingPositions}
contextMenuOpen={openContextMenuId === `folder-${f.id}`}
onContextMenuOpenChange={(open) => setOpenContextMenuId(open ? `folder-${f.id}` : null)}
isWatched={watchedFolderIds?.has(f.id)}
onRescan={onRescanFolder}
onStopWatching={onStopWatchingFolder}
/>
isWatched={watchedFolderIds?.has(f.id)}
onRescan={onRescanFolder}
onStopWatching={onStopWatchingFolder}
/>
);
if (isExpanded) {

View file

@ -1,19 +1,14 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import { Check, ChevronRight, Clock, Copy, RotateCcw } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Separator } from "@/components/ui/separator";
import { Spinner } from "@/components/ui/spinner";
import { cn } from "@/lib/utils";
import { documentsApiService } from "@/lib/apis/documents-api.service";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
interface DocumentVersionSummary {
version_number: number;
@ -123,10 +118,9 @@ function VersionHistoryPanel({ documentId }: { documentId: number }) {
setSelectedVersion(versionNumber);
setContentLoading(true);
try {
const data = (await documentsApiService.getDocumentVersion(
documentId,
versionNumber
)) as { source_markdown: string };
const data = (await documentsApiService.getDocumentVersion(documentId, versionNumber)) as {
source_markdown: string;
};
setVersionContent(data.source_markdown || "");
} catch {
toast.error("Failed to load version content");
@ -196,13 +190,11 @@ function VersionHistoryPanel({ documentId }: { documentId: number }) {
>
<div className="flex-1 min-w-0 space-y-0.5">
<p className="text-sm font-medium truncate">
{v.created_at ? formatRelativeTime(v.created_at) : `Version ${v.version_number}`}
{v.created_at
? formatRelativeTime(v.created_at)
: `Version ${v.version_number}`}
</p>
{v.title && (
<p className="text-xs text-muted-foreground truncate">
{v.title}
</p>
)}
{v.title && <p className="text-xs text-muted-foreground truncate">{v.title}</p>}
</div>
<ChevronRight className="h-3.5 w-3.5 shrink-0 opacity-50" />
</button>
@ -227,11 +219,7 @@ function VersionHistoryPanel({ documentId }: { documentId: number }) {
onClick={handleCopy}
disabled={contentLoading || copied}
>
{copied ? (
<Check className="h-3 w-3" />
) : (
<Copy className="h-3 w-3" />
)}
{copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
{copied ? "Copied" : "Copy"}
</Button>
<Button
@ -241,11 +229,7 @@ function VersionHistoryPanel({ documentId }: { documentId: number }) {
disabled={restoring || contentLoading}
onClick={() => handleRestore(selectedVersion)}
>
{restoring ? (
<Spinner size="xs" />
) : (
<RotateCcw className="h-3 w-3" />
)}
{restoring ? <Spinner size="xs" /> : <RotateCcw className="h-3 w-3" />}
Restore
</Button>
</div>

View file

@ -54,7 +54,6 @@ function EditorPanelSkeleton() {
);
}
export function EditorPanelContent({
documentId,
searchSpaceId,
@ -194,24 +193,24 @@ export function EditorPanelContent({
return (
<>
<div className="flex items-center justify-between px-4 py-2 shrink-0 border-b">
<div className="flex-1 min-w-0">
<h2 className="text-sm font-semibold truncate">{displayTitle}</h2>
{isEditableType && editedMarkdown !== null && (
<p className="text-[10px] text-muted-foreground">Unsaved changes</p>
)}
<div className="flex-1 min-w-0">
<h2 className="text-sm font-semibold truncate">{displayTitle}</h2>
{isEditableType && editedMarkdown !== null && (
<p className="text-[10px] text-muted-foreground">Unsaved changes</p>
)}
</div>
<div className="flex items-center gap-1 shrink-0">
{editorDoc?.document_type && (
<VersionHistoryButton documentId={documentId} documentType={editorDoc.document_type} />
)}
{onClose && (
<Button variant="ghost" size="icon" onClick={onClose} className="size-7 shrink-0">
<XIcon className="size-4" />
<span className="sr-only">Close editor panel</span>
</Button>
)}
</div>
</div>
<div className="flex items-center gap-1 shrink-0">
{editorDoc?.document_type && (
<VersionHistoryButton documentId={documentId} documentType={editorDoc.document_type} />
)}
{onClose && (
<Button variant="ghost" size="icon" onClick={onClose} className="size-7 shrink-0">
<XIcon className="size-4" />
<span className="sr-only">Close editor panel</span>
</Button>
)}
</div>
</div>
<div className="flex-1 overflow-hidden">
{isLoading ? (
@ -233,7 +232,9 @@ export function EditorPanelContent({
? "Document is processing"
: "Document unavailable"}
</p>
<p className="text-sm text-muted-foreground">{error || "An unknown error occurred"}</p>
<p className="text-sm text-muted-foreground">
{error || "An unknown error occurred"}
</p>
</div>
</div>
) : isLargeDocument ? (

View file

@ -121,9 +121,7 @@ export function DocumentsSidebar({
}
const recovered = await api!.getWatchedFolders();
const ids = new Set(
recovered
.filter((f) => f.rootFolderId != null)
.map((f) => f.rootFolderId as number)
recovered.filter((f) => f.rootFolderId != null).map((f) => f.rootFolderId as number)
);
setWatchedFolderIds(ids);
return;
@ -133,9 +131,7 @@ export function DocumentsSidebar({
}
const ids = new Set(
folders
.filter((f) => f.rootFolderId != null)
.map((f) => f.rootFolderId as number)
folders.filter((f) => f.rootFolderId != null).map((f) => f.rootFolderId as number)
);
setWatchedFolderIds(ids);
}
@ -305,28 +301,25 @@ export function DocumentsSidebar({
[searchSpaceId]
);
const handleStopWatching = useCallback(
async (folder: FolderDisplay) => {
const api = window.electronAPI;
if (!api) return;
const handleStopWatching = useCallback(async (folder: FolderDisplay) => {
const api = window.electronAPI;
if (!api) return;
const watchedFolders = await api.getWatchedFolders();
const matched = watchedFolders.find((wf) => wf.rootFolderId === folder.id);
if (!matched) {
toast.error("This folder is not being watched");
return;
}
const watchedFolders = await api.getWatchedFolders();
const matched = watchedFolders.find((wf) => wf.rootFolderId === folder.id);
if (!matched) {
toast.error("This folder is not being watched");
return;
}
await api.removeWatchedFolder(matched.path);
try {
await foldersApiService.stopWatching(folder.id);
} catch (err) {
console.error("[DocumentsSidebar] Failed to clear watched metadata:", err);
}
toast.success(`Stopped watching: ${matched.name}`);
},
[]
);
await api.removeWatchedFolder(matched.path);
try {
await foldersApiService.stopWatching(folder.id);
} catch (err) {
console.error("[DocumentsSidebar] Failed to clear watched metadata:", err);
}
toast.success(`Stopped watching: ${matched.name}`);
}, []);
const handleRenameFolder = useCallback(async (folder: FolderDisplay, newName: string) => {
try {
@ -755,81 +748,83 @@ export function DocumentsSidebar({
<div className="flex-1 min-h-0 overflow-x-hidden pt-0 flex flex-col">
<div className="px-4 pb-2">
<DocumentsFilters
typeCounts={typeCounts}
onSearch={setSearch}
searchValue={search}
onToggleType={onToggleType}
activeTypes={activeTypes}
onCreateFolder={() => handleCreateFolder(null)}
/>
<DocumentsFilters
typeCounts={typeCounts}
onSearch={setSearch}
searchValue={search}
onToggleType={onToggleType}
activeTypes={activeTypes}
onCreateFolder={() => handleCreateFolder(null)}
/>
</div>
<div className="relative flex-1 min-h-0 overflow-auto">
{deletableSelectedIds.length > 0 && (
<div className="absolute inset-x-0 top-0 z-10 flex items-center justify-center px-4 py-1.5 animate-in fade-in duration-150 pointer-events-none">
<button
type="button"
onClick={() => setBulkDeleteConfirmOpen(true)}
className="pointer-events-auto flex items-center gap-1.5 px-3 py-1 rounded-md bg-destructive text-destructive-foreground shadow-lg text-xs font-medium hover:bg-destructive/90 transition-colors"
>
<Trash2 size={12} />
Delete {deletableSelectedIds.length}{" "}
{deletableSelectedIds.length === 1 ? "item" : "items"}
</button>
</div>
)}
<div className="relative flex-1 min-h-0 overflow-auto">
{deletableSelectedIds.length > 0 && (
<div className="absolute inset-x-0 top-0 z-10 flex items-center justify-center px-4 py-1.5 animate-in fade-in duration-150 pointer-events-none">
<button
type="button"
onClick={() => setBulkDeleteConfirmOpen(true)}
className="pointer-events-auto flex items-center gap-1.5 px-3 py-1 rounded-md bg-destructive text-destructive-foreground shadow-lg text-xs font-medium hover:bg-destructive/90 transition-colors"
>
<Trash2 size={12} />
Delete {deletableSelectedIds.length}{" "}
{deletableSelectedIds.length === 1 ? "item" : "items"}
</button>
</div>
)}
<FolderTreeView
folders={treeFolders}
documents={searchFilteredDocuments}
expandedIds={expandedIds}
onToggleExpand={toggleFolderExpand}
mentionedDocIds={mentionedDocIds}
onToggleChatMention={handleToggleChatMention}
onToggleFolderSelect={handleToggleFolderSelect}
onRenameFolder={handleRenameFolder}
onDeleteFolder={handleDeleteFolder}
onMoveFolder={handleMoveFolder}
onCreateFolder={handleCreateFolder}
searchQuery={debouncedSearch.trim() || undefined}
onPreviewDocument={(doc) => {
openEditorPanel({
documentId: doc.id,
searchSpaceId,
title: doc.title,
});
}}
onEditDocument={(doc) => {
openEditorPanel({
documentId: doc.id,
searchSpaceId,
title: doc.title,
});
}}
onDeleteDocument={(doc) => handleDeleteDocument(doc.id)}
onMoveDocument={handleMoveDocument}
onExportDocument={handleExportDocument}
onVersionHistory={(doc) => setVersionDocId(doc.id)}
activeTypes={activeTypes}
onDropIntoFolder={handleDropIntoFolder}
onReorderFolder={handleReorderFolder}
watchedFolderIds={watchedFolderIds}
onRescanFolder={handleRescanFolder}
onStopWatchingFolder={handleStopWatching}
/>
<FolderTreeView
folders={treeFolders}
documents={searchFilteredDocuments}
expandedIds={expandedIds}
onToggleExpand={toggleFolderExpand}
mentionedDocIds={mentionedDocIds}
onToggleChatMention={handleToggleChatMention}
onToggleFolderSelect={handleToggleFolderSelect}
onRenameFolder={handleRenameFolder}
onDeleteFolder={handleDeleteFolder}
onMoveFolder={handleMoveFolder}
onCreateFolder={handleCreateFolder}
searchQuery={debouncedSearch.trim() || undefined}
onPreviewDocument={(doc) => {
openEditorPanel({
documentId: doc.id,
searchSpaceId,
title: doc.title,
});
}}
onEditDocument={(doc) => {
openEditorPanel({
documentId: doc.id,
searchSpaceId,
title: doc.title,
});
}}
onDeleteDocument={(doc) => handleDeleteDocument(doc.id)}
onMoveDocument={handleMoveDocument}
onExportDocument={handleExportDocument}
onVersionHistory={(doc) => setVersionDocId(doc.id)}
activeTypes={activeTypes}
onDropIntoFolder={handleDropIntoFolder}
onReorderFolder={handleReorderFolder}
watchedFolderIds={watchedFolderIds}
onRescanFolder={handleRescanFolder}
onStopWatchingFolder={handleStopWatching}
/>
</div>
</div>
</div>
{versionDocId !== null && (
<VersionHistoryDialog
open
onOpenChange={(open) => { if (!open) setVersionDocId(null); }}
documentId={versionDocId}
/>
)}
{versionDocId !== null && (
<VersionHistoryDialog
open
onOpenChange={(open) => {
if (!open) setVersionDocId(null);
}}
documentId={versionDocId}
/>
)}
<FolderPickerDialog
<FolderPickerDialog
open={folderPickerOpen}
onOpenChange={setFolderPickerOpen}
folders={treeFolders}

View file

@ -185,9 +185,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
<p className="font-semibold text-foreground text-lg">
{isProcessing ? "Document is processing" : "Document unavailable"}
</p>
<p className="text-sm text-muted-foreground">
{error || "An unknown error occurred"}
</p>
<p className="text-sm text-muted-foreground">{error || "An unknown error occurred"}</p>
</div>
{!isProcessing && (
<Button

View file

@ -480,9 +480,7 @@ export function SourceDetailPanel({
<FileQuestionMark className="h-10 w-10 text-muted-foreground" />
</div>
<div>
<p className="font-semibold text-foreground text-lg">
Document unavailable
</p>
<p className="font-semibold text-foreground text-lg">Document unavailable</p>
<p className="text-sm text-muted-foreground mt-2 max-w-md">
{documentByChunkFetchingError.message ||
"An unexpected error occurred. Please try again."}

View file

@ -134,24 +134,27 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
preferences?.image_generation_config_id,
]);
const handleRoleAssignment = useCallback(async (prefKey: string, configId: string) => {
const value = configId === "unassigned" ? "" : parseInt(configId);
const handleRoleAssignment = useCallback(
async (prefKey: string, configId: string) => {
const value = configId === "unassigned" ? "" : parseInt(configId);
setAssignments((prev) => ({ ...prev, [prefKey]: value }));
setSavingRole(prefKey);
savingRef.current = true;
setAssignments((prev) => ({ ...prev, [prefKey]: value }));
setSavingRole(prefKey);
savingRef.current = true;
try {
await updatePreferences({
search_space_id: searchSpaceId,
data: { [prefKey]: value || undefined },
});
toast.success("Role assignment updated");
} finally {
setSavingRole(null);
savingRef.current = false;
}
}, [updatePreferences, searchSpaceId]);
try {
await updatePreferences({
search_space_id: searchSpaceId,
data: { [prefKey]: value || undefined },
});
toast.success("Role assignment updated");
} finally {
setSavingRole(null);
savingRef.current = false;
}
},
[updatePreferences, searchSpaceId]
);
// Combine global and custom LLM configs
const allLLMConfigs = [
@ -199,10 +202,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
Refresh
</Button>
{isAssignmentComplete && !isLoading && !hasError && (
<Badge
variant="outline"
className="text-xs gap-1.5 text-muted-foreground"
>
<Badge variant="outline" className="text-xs gap-1.5 text-muted-foreground">
<CircleCheck className="h-3 w-3" />
All roles assigned
</Badge>
@ -483,7 +483,6 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
})}
</div>
)}
</div>
);
}

View file

@ -128,7 +128,8 @@ const MAX_TOTAL_SIZE_BYTES = MAX_TOTAL_SIZE_MB * 1024 * 1024;
const MAX_FILE_SIZE_MB = 500;
const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
const toggleRowClass = "flex items-center justify-between rounded-lg bg-slate-400/5 dark:bg-white/5 p-3";
const toggleRowClass =
"flex items-center justify-between rounded-lg bg-slate-400/5 dark:bg-white/5 p-3";
export function DocumentUploadTab({
searchSpaceId,
@ -326,7 +327,14 @@ export function DocumentUploadTab({
await api.addWatchedFolder({
path: selectedFolder.path,
name: selectedFolder.name,
excludePatterns: [".git", "node_modules", "__pycache__", ".DS_Store", ".obsidian", ".trash"],
excludePatterns: [
".git",
"node_modules",
"__pycache__",
".DS_Store",
".obsidian",
".trash",
],
fileExtensions: null,
rootFolderId,
searchSpaceId: Number(searchSpaceId),
@ -393,12 +401,20 @@ export function DocumentUploadTab({
return (
<DropdownMenu>
<DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>
<Button variant="ghost" size="sm" className={`text-xs gap-1 bg-neutral-700/50 hover:bg-neutral-600/50 ${sizeClass} ${widthClass}`}>
<Button
variant="ghost"
size="sm"
className={`text-xs gap-1 bg-neutral-700/50 hover:bg-neutral-600/50 ${sizeClass} ${widthClass}`}
>
Browse
<ChevronDown className="h-3 w-3 opacity-60" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="center" className="dark:bg-neutral-800" onClick={(e) => e.stopPropagation()}>
<DropdownMenuContent
align="center"
className="dark:bg-neutral-800"
onClick={(e) => e.stopPropagation()}
>
<DropdownMenuItem onClick={handleBrowseFiles}>
<FileIcon className="h-4 w-4 mr-2" />
Files
@ -415,7 +431,11 @@ export function DocumentUploadTab({
return (
<DropdownMenu>
<DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>
<Button variant="secondary" size="sm" className={`text-xs gap-1 ${sizeClass} ${widthClass}`}>
<Button
variant="secondary"
size="sm"
className={`text-xs gap-1 ${sizeClass} ${widthClass}`}
>
Browse
<ChevronDown className="h-3 w-3 opacity-60" />
</Button>
@ -457,21 +477,19 @@ export function DocumentUploadTab({
{/* MOBILE DROP ZONE */}
<div className="sm:hidden">
{hasContent ? (
!selectedFolder && !isFileCountLimitReached && (
isElectron ? (
<div className="w-full">
{renderBrowseButton({ compact: true, fullWidth: true })}
</div>
) : (
<button
type="button"
className="w-full text-xs h-8 flex items-center justify-center gap-1.5 rounded-md border border-dashed border-muted-foreground/30 text-muted-foreground hover:text-foreground hover:border-foreground/50 transition-colors"
onClick={() => fileInputRef.current?.click()}
>
Add more files
</button>
)
)
!selectedFolder &&
!isFileCountLimitReached &&
(isElectron ? (
<div className="w-full">{renderBrowseButton({ compact: true, fullWidth: true })}</div>
) : (
<button
type="button"
className="w-full text-xs h-8 flex items-center justify-center gap-1.5 rounded-md border border-dashed border-muted-foreground/30 text-muted-foreground hover:text-foreground hover:border-foreground/50 transition-colors"
onClick={() => fileInputRef.current?.click()}
>
Add more files
</button>
))
) : (
<div
className="flex flex-col items-center gap-4 py-12 px-4 cursor-pointer"
@ -487,7 +505,9 @@ export function DocumentUploadTab({
<p className="text-sm text-muted-foreground inline-flex items-center flex-wrap justify-center">
<span>{t("file_size_limit")}</span>
<Dot className="h-4 w-4 shrink-0" />
<span>{t("upload_limits", { maxFiles: MAX_FILES, maxSizeMB: MAX_TOTAL_SIZE_MB })}</span>
<span>
{t("upload_limits", { maxFiles: MAX_FILES, maxSizeMB: MAX_TOTAL_SIZE_MB })}
</span>
</p>
</div>
<div className="w-full mt-1" onClick={(e) => e.stopPropagation()}>
@ -538,7 +558,9 @@ export function DocumentUploadTab({
<p className="text-xs text-muted-foreground text-center inline-flex items-center flex-wrap justify-center">
<span>{t("file_size_limit")}</span>
<Dot className="h-4 w-4 shrink-0" />
<span>{t("upload_limits", { maxFiles: MAX_FILES, maxSizeMB: MAX_TOTAL_SIZE_MB })}</span>
<span>
{t("upload_limits", { maxFiles: MAX_FILES, maxSizeMB: MAX_TOTAL_SIZE_MB })}
</span>
</p>
<div className="mt-1">{renderBrowseButton()}</div>
</div>
@ -569,9 +591,7 @@ export function DocumentUploadTab({
<div className="flex items-center justify-between p-3">
<div className="space-y-0.5">
<p className="font-medium text-sm">Watch folder</p>
<p className="text-xs text-muted-foreground">
Auto-sync when files change
</p>
<p className="text-xs text-muted-foreground">Auto-sync when files change</p>
</div>
<Switch
id="watch-folder-toggle"
@ -612,7 +632,8 @@ export function DocumentUploadTab({
<div className="rounded-lg border border-border p-3 space-y-2">
<div className="flex items-center justify-between">
<p className="text-sm font-medium">
{t("selected_files", { count: files.length })} &middot; {formatFileSize(totalFileSize)}
{t("selected_files", { count: files.length })} &middot;{" "}
{formatFileSize(totalFileSize)}
</p>
<Button
variant="ghost"

View file

@ -404,7 +404,6 @@ class ConnectorsApiService {
listDiscordChannelsResponse
);
};
}
export type { SlackChannel, DiscordChannel };

View file

@ -417,27 +417,47 @@ class DocumentsApiService {
};
getDocumentVersion = async (documentId: number, versionNumber: number) => {
return baseApiService.get(
`/api/v1/documents/${documentId}/versions/${versionNumber}`
);
return baseApiService.get(`/api/v1/documents/${documentId}/versions/${versionNumber}`);
};
restoreDocumentVersion = async (documentId: number, versionNumber: number) => {
return baseApiService.post(
`/api/v1/documents/${documentId}/versions/${versionNumber}/restore`
);
return baseApiService.post(`/api/v1/documents/${documentId}/versions/${versionNumber}/restore`);
};
folderIndex = async (searchSpaceId: number, body: { folder_path: string; folder_name: string; search_space_id: number; exclude_patterns?: string[]; file_extensions?: string[]; root_folder_id?: number; enable_summary?: boolean }) => {
folderIndex = async (
searchSpaceId: number,
body: {
folder_path: string;
folder_name: string;
search_space_id: number;
exclude_patterns?: string[];
file_extensions?: string[];
root_folder_id?: number;
enable_summary?: boolean;
}
) => {
return baseApiService.post(`/api/v1/documents/folder-index`, undefined, { body });
};
folderIndexFiles = async (searchSpaceId: number, body: { folder_path: string; folder_name: string; search_space_id: number; target_file_paths: string[]; root_folder_id?: number | null; enable_summary?: boolean }) => {
folderIndexFiles = async (
searchSpaceId: number,
body: {
folder_path: string;
folder_name: string;
search_space_id: number;
target_file_paths: string[];
root_folder_id?: number | null;
enable_summary?: boolean;
}
) => {
return baseApiService.post(`/api/v1/documents/folder-index-files`, undefined, { body });
};
getWatchedFolders = async (searchSpaceId: number) => {
return baseApiService.get(`/api/v1/documents/watched-folders?search_space_id=${searchSpaceId}`, folderListResponse);
return baseApiService.get(
`/api/v1/documents/watched-folders?search_space_id=${searchSpaceId}`,
folderListResponse
);
};
/**