mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
Merge branch 'main' into 03-10-feat_empty_state_action_for_new_spaces
This commit is contained in:
commit
f015053a2b
6 changed files with 383 additions and 13 deletions
5
.github/workflows/claude-code-review.yml
vendored
5
.github/workflows/claude-code-review.yml
vendored
|
|
@ -6,7 +6,10 @@ on:
|
|||
|
||||
jobs:
|
||||
claude-review:
|
||||
if: github.event.pull_request.draft == false
|
||||
if: |
|
||||
github.event.pull_request.draft == false &&
|
||||
github.actor != 'graphite-app[bot]' &&
|
||||
github.actor != 'dependabot[bot]'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
|
|
|
|||
|
|
@ -155,13 +155,66 @@ export default function NewPage() {
|
|||
const resetDraft = useQuickNoteDraftReset(selectedProject)
|
||||
const { draft: quickNoteDraft } = useQuickNoteDraft(selectedProject || "")
|
||||
|
||||
const { noteMutation } = useDocumentMutations({
|
||||
const { noteMutation, bulkDeleteMutation } = useDocumentMutations({
|
||||
onClose: () => {
|
||||
resetDraft()
|
||||
setIsFullscreen(false)
|
||||
},
|
||||
})
|
||||
|
||||
const [selectedDocumentIds, setSelectedDocumentIds] = useState<Set<string>>(
|
||||
new Set(),
|
||||
)
|
||||
const [isSelectionMode, setIsSelectionMode] = useState(false)
|
||||
|
||||
const handleToggleSelection = useCallback((documentId: string) => {
|
||||
setSelectedDocumentIds((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(documentId)) {
|
||||
next.delete(documentId)
|
||||
} else {
|
||||
next.add(documentId)
|
||||
}
|
||||
return next
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleClearSelection = useCallback(() => {
|
||||
setSelectedDocumentIds(new Set())
|
||||
setIsSelectionMode(false)
|
||||
}, [])
|
||||
|
||||
const handleEnterSelectionMode = useCallback(() => {
|
||||
setIsSelectionMode(true)
|
||||
}, [])
|
||||
|
||||
const handleSelectAllVisible = useCallback((visibleIds: string[]) => {
|
||||
setSelectedDocumentIds((prev) => {
|
||||
const next = new Set(prev)
|
||||
for (const id of visibleIds) {
|
||||
next.add(id)
|
||||
}
|
||||
return next
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleBulkDelete = useCallback(() => {
|
||||
const ids = Array.from(selectedDocumentIds)
|
||||
if (ids.length === 0) return
|
||||
bulkDeleteMutation.mutate(
|
||||
{ documentIds: ids },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setSelectedDocumentIds(new Set())
|
||||
setIsSelectionMode(false)
|
||||
if (selectedDocument && ids.includes(selectedDocument.id ?? "")) {
|
||||
setDocId(null)
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
}, [selectedDocumentIds, bulkDeleteMutation, selectedDocument, setDocId])
|
||||
|
||||
type SpaceHighlightsResponse = {
|
||||
highlights: HighlightItem[]
|
||||
questions: string[]
|
||||
|
|
@ -387,6 +440,14 @@ export default function NewPage() {
|
|||
<MemoriesGrid
|
||||
isChatOpen={chatOpen}
|
||||
onOpenDocument={handleOpenDocument}
|
||||
isSelectionMode={isSelectionMode}
|
||||
selectedDocumentIds={selectedDocumentIds}
|
||||
onEnterSelectionMode={handleEnterSelectionMode}
|
||||
onToggleSelection={handleToggleSelection}
|
||||
onClearSelection={handleClearSelection}
|
||||
onSelectAllVisible={handleSelectAllVisible}
|
||||
onBulkDelete={handleBulkDelete}
|
||||
isBulkDeleting={bulkDeleteMutation.isPending}
|
||||
quickNoteProps={{
|
||||
onSave: handleQuickNoteSave,
|
||||
onMaximize: handleMaximize,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,17 @@ import { GraphCard } from "./memory-graph"
|
|||
import { Button } from "@ui/components/button"
|
||||
import { categoriesParam } from "@/lib/search-params"
|
||||
import { NovaEmptyState } from "@/components/nova/nova-empty-state"
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@ui/components/alert-dialog"
|
||||
import { CheckIcon, Trash2Icon, XIcon } from "lucide-react"
|
||||
|
||||
// Document category type
|
||||
type DocumentCategory =
|
||||
|
|
@ -94,6 +105,14 @@ interface NovaEmptyStateProps {
|
|||
interface MemoriesGridProps {
|
||||
isChatOpen: boolean
|
||||
onOpenDocument: (document: DocumentWithMemories) => void
|
||||
isSelectionMode?: boolean
|
||||
selectedDocumentIds?: Set<string>
|
||||
onEnterSelectionMode?: () => void
|
||||
onToggleSelection?: (documentId: string) => void
|
||||
onClearSelection?: () => void
|
||||
onSelectAllVisible?: (visibleIds: string[]) => void
|
||||
onBulkDelete?: () => void
|
||||
isBulkDeleting?: boolean
|
||||
quickNoteProps?: QuickNoteProps
|
||||
highlightsProps?: HighlightsProps
|
||||
emptyStateProps?: NovaEmptyStateProps
|
||||
|
|
@ -102,10 +121,19 @@ interface MemoriesGridProps {
|
|||
export function MemoriesGrid({
|
||||
isChatOpen,
|
||||
onOpenDocument,
|
||||
isSelectionMode = false,
|
||||
selectedDocumentIds = new Set(),
|
||||
onEnterSelectionMode,
|
||||
onToggleSelection,
|
||||
onClearSelection,
|
||||
onSelectAllVisible,
|
||||
onBulkDelete,
|
||||
isBulkDeleting = false,
|
||||
quickNoteProps,
|
||||
highlightsProps,
|
||||
emptyStateProps,
|
||||
}: MemoriesGridProps) {
|
||||
const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false)
|
||||
const { user } = useAuth()
|
||||
const { effectiveContainerTags } = useProject()
|
||||
const isMobile = useIsMobile()
|
||||
|
|
@ -253,11 +281,31 @@ export function MemoriesGrid({
|
|||
|
||||
const handleCardClick = useCallback(
|
||||
(document: DocumentWithMemories) => {
|
||||
onOpenDocument(document)
|
||||
if (isSelectionMode && onToggleSelection && document.id) {
|
||||
onToggleSelection(document.id)
|
||||
} else {
|
||||
onOpenDocument(document)
|
||||
}
|
||||
},
|
||||
[onOpenDocument],
|
||||
[isSelectionMode, onToggleSelection, onOpenDocument],
|
||||
)
|
||||
|
||||
const handleSelectAllVisible = useCallback(() => {
|
||||
if (onSelectAllVisible) {
|
||||
onSelectAllVisible(documents.map((d) => d.id).filter(Boolean) as string[])
|
||||
}
|
||||
}, [documents, onSelectAllVisible])
|
||||
|
||||
const handleBulkDeleteClick = useCallback(() => {
|
||||
if (selectedDocumentIds.size === 0) return
|
||||
setShowBulkDeleteConfirm(true)
|
||||
}, [selectedDocumentIds.size])
|
||||
|
||||
const handleBulkDeleteConfirm = useCallback(() => {
|
||||
setShowBulkDeleteConfirm(false)
|
||||
onBulkDelete?.()
|
||||
}, [onBulkDelete])
|
||||
|
||||
const renderMasonryItem = useCallback(
|
||||
({
|
||||
index,
|
||||
|
|
@ -269,13 +317,21 @@ export function MemoriesGrid({
|
|||
width: number
|
||||
}) => {
|
||||
if (data.type === "document") {
|
||||
const doc = data.data
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<DocumentCard
|
||||
index={index}
|
||||
data={data.data}
|
||||
data={doc}
|
||||
width={width}
|
||||
onClick={handleCardClick}
|
||||
isSelectionMode={isSelectionMode}
|
||||
isSelected={doc.id ? selectedDocumentIds.has(doc.id) : false}
|
||||
onToggleSelection={
|
||||
doc.id && onToggleSelection
|
||||
? () => onToggleSelection(doc.id as string)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
|
|
@ -283,7 +339,7 @@ export function MemoriesGrid({
|
|||
|
||||
return null
|
||||
},
|
||||
[handleCardClick],
|
||||
[handleCardClick, isSelectionMode, selectedDocumentIds, onToggleSelection],
|
||||
)
|
||||
|
||||
if (!user) {
|
||||
|
|
@ -303,6 +359,11 @@ export function MemoriesGrid({
|
|||
<div className="relative">
|
||||
{!isEmpty && (
|
||||
<div id="filter-pills" className="flex flex-wrap gap-1.5 mb-3">
|
||||
<div
|
||||
id="filter-pills"
|
||||
className="flex items-center justify-between gap-4 mb-3"
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<Button
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
|
|
@ -334,6 +395,105 @@ export function MemoriesGrid({
|
|||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{isSelectionMode && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Exit selection mode"
|
||||
className="w-8 h-8 flex items-center justify-center rounded-full border border-[#161F2C] bg-[#0D121A] hover:bg-[#00173C] transition-colors cursor-pointer"
|
||||
onClick={onClearSelection}
|
||||
>
|
||||
<XIcon className="w-4 h-4 text-[#737373]" />
|
||||
</button>
|
||||
{selectedDocumentIds.size > 0 ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"text-xs text-[#737373] hover:text-white transition-colors cursor-pointer",
|
||||
)}
|
||||
onClick={handleSelectAllVisible}
|
||||
>
|
||||
Select all
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"flex items-center gap-1 text-xs text-red-400 hover:text-red-300 transition-colors cursor-pointer disabled:opacity-50",
|
||||
)}
|
||||
onClick={handleBulkDeleteClick}
|
||||
disabled={isBulkDeleting}
|
||||
>
|
||||
<Trash2Icon className="w-3 h-3" />
|
||||
Delete ({selectedDocumentIds.size})
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<p className={cn(dmSansClassName(), "text-xs text-[#737373]")}>
|
||||
Select one or more documents
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!isSelectionMode && onEnterSelectionMode && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Enter selection mode"
|
||||
className="w-8 h-8 flex items-center justify-center rounded-full border border-[#161F2C] bg-[#0D121A] hover:bg-[#00173C] transition-colors cursor-pointer"
|
||||
onClick={onEnterSelectionMode}
|
||||
>
|
||||
<div className="w-3 h-3 rounded-[2.25px] border border-[#737373]" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AlertDialog
|
||||
open={showBulkDeleteConfirm}
|
||||
onOpenChange={setShowBulkDeleteConfirm}
|
||||
>
|
||||
<AlertDialogContent
|
||||
className={cn(
|
||||
"border-none bg-[#1B1F24] p-4 gap-4 rounded-[22px] max-w-[400px]",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
style={{
|
||||
boxShadow:
|
||||
"0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset",
|
||||
}}
|
||||
>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="text-[#FAFAFA] font-medium">
|
||||
Delete selected memories?
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="text-[#737373]">
|
||||
This will permanently delete {selectedDocumentIds.size}{" "}
|
||||
{selectedDocumentIds.size === 1 ? "memory" : "memories"}. This
|
||||
action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter className="flex-row gap-2 sm:justify-end">
|
||||
<AlertDialogCancel
|
||||
className="border-none bg-transparent text-[#737373] hover:bg-[#14161A]/50 hover:text-white rounded-full cursor-pointer"
|
||||
onClick={() => setShowBulkDeleteConfirm(false)}
|
||||
>
|
||||
Cancel
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className="bg-red-600 hover:bg-red-700 text-white border-none rounded-[10px] cursor-pointer"
|
||||
onClick={handleBulkDeleteConfirm}
|
||||
disabled={isBulkDeleting}
|
||||
>
|
||||
{isBulkDeleting ? "Deleting…" : "Delete"}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{error ? (
|
||||
<div className="h-full flex items-center justify-center p-4">
|
||||
<div className="text-center text-muted-foreground">
|
||||
|
|
@ -435,18 +595,31 @@ function DocumentUrlDisplay({ url }: { url: string }) {
|
|||
)
|
||||
}
|
||||
|
||||
function isTemporaryId(id: string | null | undefined): boolean {
|
||||
if (!id) return false
|
||||
return id.startsWith("temp-") || id.startsWith("temp-file-")
|
||||
}
|
||||
|
||||
const DocumentCard = memo(
|
||||
({
|
||||
index: _index,
|
||||
data: document,
|
||||
width,
|
||||
onClick,
|
||||
isSelectionMode = false,
|
||||
isSelected = false,
|
||||
onToggleSelection,
|
||||
}: {
|
||||
index: number
|
||||
data: DocumentWithMemories
|
||||
width: number
|
||||
onClick: (document: DocumentWithMemories) => void
|
||||
isSelectionMode?: boolean
|
||||
isSelected?: boolean
|
||||
onToggleSelection?: () => void
|
||||
}) => {
|
||||
const canSelect =
|
||||
!isTemporaryId(document.id) && !isTemporaryId(document.customId)
|
||||
const [rotation, setRotation] = useState({ rotateX: 0, rotateY: 0 })
|
||||
const cardRef = useRef<HTMLButtonElement>(null)
|
||||
const [ogData, setOgData] = useState<OgData | null>(null)
|
||||
|
|
@ -489,8 +662,12 @@ const DocumentCard = memo(
|
|||
}
|
||||
}, [needsOgData, ogData, isLoadingOg, document.url])
|
||||
|
||||
useEffect(() => {
|
||||
if (isSelectionMode) setRotation({ rotateX: 0, rotateY: 0 })
|
||||
}, [isSelectionMode])
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (!cardRef.current) return
|
||||
if (isSelectionMode || !cardRef.current) return
|
||||
|
||||
const rect = cardRef.current.getBoundingClientRect()
|
||||
const centerX = rect.left + rect.width / 2
|
||||
|
|
@ -511,12 +688,32 @@ const DocumentCard = memo(
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="p-2" style={{ width }}>
|
||||
<div className="p-2 relative" style={{ width }}>
|
||||
{isSelectionMode && canSelect && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={isSelected ? "Deselect" : "Select"}
|
||||
className="absolute top-5 right-5 z-10 flex items-center justify-center cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onToggleSelection?.()
|
||||
}}
|
||||
>
|
||||
{isSelected ? (
|
||||
<div className="w-3 h-3 rounded-[2.25px] border border-[#369BFD] bg-[#369BFD] flex items-center justify-center">
|
||||
<CheckIcon className="w-2 h-2 text-white" strokeWidth={3} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-3 h-3 rounded-[2.25px] border border-[#737373]" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
id={document.id ? `document-card-${document.id}` : undefined}
|
||||
ref={cardRef}
|
||||
type="button"
|
||||
className={cn(
|
||||
"rounded-[22px] bg-[#1B1F24] px-1 space-y-2 pt-1 cursor-pointer w-full",
|
||||
"rounded-[22px] bg-[#1B1F24] px-1 space-y-2 pt-1 cursor-pointer w-full relative overflow-hidden",
|
||||
"border-none text-left transition-transform duration-200 ease-out",
|
||||
document.type === "image" ||
|
||||
document.metadata?.mimeType?.toString().startsWith("image/")
|
||||
|
|
@ -529,10 +726,15 @@ const DocumentCard = memo(
|
|||
style={{
|
||||
boxShadow:
|
||||
"0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset",
|
||||
transform: `perspective(1000px) rotateX(${rotation.rotateX}deg) rotateY(${rotation.rotateY}deg)`,
|
||||
transformStyle: "preserve-3d",
|
||||
transform: isSelectionMode
|
||||
? "none"
|
||||
: `perspective(1000px) rotateX(${rotation.rotateX}deg) rotateY(${rotation.rotateY}deg)`,
|
||||
transformStyle: isSelectionMode ? undefined : "preserve-3d",
|
||||
}}
|
||||
>
|
||||
{isSelectionMode && isSelected && (
|
||||
<div className="absolute inset-0 bg-[rgba(75,160,250,0.25)] rounded-[22px] z-1 pointer-events-none" />
|
||||
)}
|
||||
<ContentPreview document={document} ogData={ogData} />
|
||||
{!(
|
||||
document.type === "image" ||
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@ type InfiniteQueryData = {
|
|||
pageParams: number[]
|
||||
}
|
||||
|
||||
type QueryData = DocumentsQueryData | InfiniteQueryData
|
||||
|
||||
interface UseDocumentMutationsOptions {
|
||||
onClose?: () => void
|
||||
}
|
||||
|
|
@ -140,6 +138,62 @@ function removeDocumentFromQueryData(
|
|||
return old
|
||||
}
|
||||
|
||||
function removeDocumentsFromQueryData(
|
||||
old: unknown,
|
||||
documentIds: Set<string>,
|
||||
): unknown {
|
||||
if (!old || typeof old !== "object" || documentIds.size === 0) return old
|
||||
|
||||
const data = old as Record<string, unknown>
|
||||
|
||||
if ("pages" in data && Array.isArray(data.pages)) {
|
||||
return {
|
||||
...data,
|
||||
pages: data.pages.map((page: unknown) => {
|
||||
const p = page as Record<string, unknown>
|
||||
if (!p?.documents || !Array.isArray(p.documents)) return page
|
||||
const filtered = (p.documents as DocumentWithId[]).filter(
|
||||
(doc) =>
|
||||
!documentIds.has(doc.id ?? "") &&
|
||||
!documentIds.has(doc.customId ?? ""),
|
||||
)
|
||||
const removed =
|
||||
(p.documents as DocumentWithId[]).length - filtered.length
|
||||
return {
|
||||
...p,
|
||||
documents: filtered,
|
||||
pagination: p.pagination
|
||||
? {
|
||||
...(p.pagination as Record<string, unknown>),
|
||||
totalItems: Math.max(
|
||||
0,
|
||||
((p.pagination as Record<string, number>).totalItems ?? 0) -
|
||||
removed,
|
||||
),
|
||||
}
|
||||
: p.pagination,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
if ("documents" in data && Array.isArray(data.documents)) {
|
||||
const filtered = (data.documents as DocumentWithId[]).filter(
|
||||
(doc) =>
|
||||
!documentIds.has(doc.id ?? "") && !documentIds.has(doc.customId ?? ""),
|
||||
)
|
||||
const removed =
|
||||
(data.documents as DocumentWithId[]).length - filtered.length
|
||||
return {
|
||||
...data,
|
||||
documents: filtered,
|
||||
totalCount: Math.max(0, ((data.totalCount as number) ?? 0) - removed),
|
||||
}
|
||||
}
|
||||
|
||||
return old
|
||||
}
|
||||
|
||||
async function cancelAndSnapshotQueries(
|
||||
queryClient: QueryClient,
|
||||
): Promise<[unknown, unknown][]> {
|
||||
|
|
@ -460,11 +514,50 @@ export function useDocumentMutations({
|
|||
},
|
||||
})
|
||||
|
||||
const bulkDeleteMutation = useMutation({
|
||||
mutationFn: async ({ documentIds }: { documentIds: string[] }) => {
|
||||
const response = await $fetch("@delete/documents/bulk", {
|
||||
body: { ids: documentIds },
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error?.message || "Failed to delete documents")
|
||||
}
|
||||
|
||||
return response.data
|
||||
},
|
||||
onMutate: async ({ documentIds }) => {
|
||||
const previousQueries = await cancelAndSnapshotQueries(queryClient)
|
||||
const idSet = new Set(documentIds)
|
||||
|
||||
queryClient.setQueriesData(
|
||||
{ queryKey: ["documents-with-memories"] },
|
||||
(old) => removeDocumentsFromQueryData(old, idSet),
|
||||
)
|
||||
|
||||
return { previousQueries }
|
||||
},
|
||||
onError: (error, _variables, context) => {
|
||||
restoreQueriesFromSnapshot(queryClient, context?.previousQueries)
|
||||
toast.error("Failed to delete documents", {
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
})
|
||||
},
|
||||
onSuccess: (_data, variables) => {
|
||||
analytics.documentsBulkDeleted({ count: variables.documentIds.length })
|
||||
toast.success(
|
||||
`${variables.documentIds.length} document${variables.documentIds.length === 1 ? "" : "s"} deleted`,
|
||||
)
|
||||
queryClient.invalidateQueries({ queryKey: ["documents-with-memories"] })
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
noteMutation,
|
||||
linkMutation,
|
||||
fileMutation,
|
||||
updateMutation,
|
||||
deleteMutation,
|
||||
bulkDeleteMutation,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,6 +161,9 @@ export const analytics = {
|
|||
documentDeleted: (props: { document_id: string }) =>
|
||||
safeCapture("document_deleted", props),
|
||||
|
||||
documentsBulkDeleted: (props: { count: number }) =>
|
||||
safeCapture("documents_bulk_deleted", props),
|
||||
|
||||
documentEdited: (props: { document_id: string }) =>
|
||||
safeCapture("document_edited", props),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import {
|
|||
AnalyticsChatResponseSchema,
|
||||
AnalyticsMemoryResponseSchema,
|
||||
AnalyticsUsageResponseSchema,
|
||||
BulkDeleteMemoriesResponseSchema,
|
||||
BulkDeleteMemoriesSchema,
|
||||
ConnectionResponseSchema,
|
||||
CreateProjectSchema,
|
||||
DeleteProjectResponseSchema,
|
||||
|
|
@ -170,6 +172,12 @@ export const apiSchema = createSchema({
|
|||
params: z.object({ id: z.string() }),
|
||||
},
|
||||
|
||||
// Bulk delete memories
|
||||
"@delete/documents/bulk": {
|
||||
body: BulkDeleteMemoriesSchema,
|
||||
output: BulkDeleteMemoriesResponseSchema,
|
||||
},
|
||||
|
||||
// Search operations
|
||||
"@post/search": {
|
||||
input: SearchRequestSchema,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue