Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions apps/sim/app/api/v1/knowledge/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '@/lib/core/orchestration/types'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { performCreateKnowledgeBase } from '@/lib/knowledge/orchestration'
import { getKnowledgeBases } from '@/lib/knowledge/service'
import { listWorkspaceAndLegacyKnowledgeBases } from '@/lib/knowledge/service'
import { formatKnowledgeBase, handleError } from '@/app/api/v1/knowledge/utils'
import {
authenticateRequest,
Expand Down Expand Up @@ -43,7 +43,9 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
const accessError = await validateWorkspaceAccess(rateLimit, userId, workspaceId)
if (accessError) return accessError

const knowledgeBases = await getKnowledgeBases(userId, workspaceId)
/** Read only after `validateWorkspaceAccess` authorized this caller; same list the
* internal surface serves, from the same place. */
const knowledgeBases = await listWorkspaceAndLegacyKnowledgeBases(userId, workspaceId)

return NextResponse.json({
success: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
ChipModalHeader,
handleKeyboardActivation,
Label,
Trash,
} from '@sim/emcn'
import { Trash } from '@sim/emcn/icons'
import { createLogger } from '@sim/logger'
import { formatDate } from '@sim/utils/formatting'
import {
Expand Down Expand Up @@ -378,11 +378,7 @@ export function DocumentTagsModal({

return (
<ChipModal open={open} onOpenChange={handleClose} srTitle='Document Tags' size='sm'>
<ChipModalHeader onClose={() => handleClose(false)}>
<div className='flex items-center justify-between'>
<span>Document Tags</span>
</div>
</ChipModalHeader>
<ChipModalHeader onClose={() => handleClose(false)}>Document Tags</ChipModalHeader>

<ChipModalBody>
<ChipModalField type='custom' title='Tags'>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
'use client'

import { useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } from 'react'
import { Badge, ChipCombobox, ChipConfirmModal, Plus, Trash } from '@sim/emcn'
import { ChevronDown, ChevronUp, Database, FileText, Pencil, TagIcon } from '@sim/emcn/icons'
import { Badge, ChipCombobox, ChipConfirmModal, chipContentLabelClass, cn } from '@sim/emcn'
import {
ChevronDown,
ChevronUp,
Database,
FileText,
Pencil,
Plus,
TagIcon,
Trash,
} from '@sim/emcn/icons'
import { createLogger } from '@sim/logger'
import { truncate } from '@sim/utils/string'
import { useParams, useRouter } from 'next/navigation'
Expand Down Expand Up @@ -204,7 +213,6 @@ export function Document({
chunks: initialChunks,
currentPage: initialPage,
totalPages: initialTotalPages,
goToPage: initialGoToPage,
error: initialError,
updateChunk: initialUpdateChunk,
} = useDocumentChunks(
Expand Down Expand Up @@ -292,26 +300,22 @@ export function Document({
const totalPagesRef = useRef(totalPages)
totalPagesRef.current = totalPages

const goToPage = useCallback(
async (page: number) => {
await setDocumentParams({ page })

if (showingSearch) {
return
}
return initialGoToPage(page)
},
[showingSearch, initialGoToPage, setDocumentParams]
)
const goToPage = useCallback((page: number) => setDocumentParams({ page }), [setDocumentParams])

const updateChunk = showingSearch
? (_id: string, _updates: Record<string, unknown>) => {}
: initialUpdateChunk

const [chunkToDelete, setChunkToDelete] = useState<ChunkData | null>(null)
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
const [showDeleteDocumentDialog, setShowDeleteDocumentDialog] = useState(false)
const [contextMenuChunk, setContextMenuChunk] = useState<ChunkData | null>(null)
const [contextMenuChunkId, setContextMenuChunkId] = useState<string | null>(null)
/**
* The id, not the row: the chunk list polls while a document processes, and a menu that
* captured the row on open would keep offering "Enable" for a chunk already enabled.
*/
const contextMenuChunk = contextMenuChunkId
? (displayChunks.find((chunk) => chunk.id === contextMenuChunkId) ?? null)
: null

const { mutate: updateChunkMutation } = useUpdateChunk()
const { mutate: deleteDocumentMutation, isPending: isDeletingDocument } = useDeleteDocument()
Expand Down Expand Up @@ -351,15 +355,10 @@ export function Document({

const isInEditorView = selectedChunkId !== null || isCreatingNewChunk

const selectedChunk = useMemo(
() => (selectedChunkId ? (displayChunks.find((c) => c.id === selectedChunkId) ?? null) : null),
[selectedChunkId, displayChunks]
)

const currentChunkIndex = useMemo(
() => (selectedChunk ? displayChunks.findIndex((c) => c.id === selectedChunk.id) : -1),
[selectedChunk, displayChunks]
)
const currentChunkIndex = selectedChunkId
? displayChunks.findIndex((chunk) => chunk.id === selectedChunkId)
: -1
const selectedChunk = currentChunkIndex >= 0 ? displayChunks[currentChunkIndex] : null
const canNavigatePrev = currentChunkIndex > 0 || currentPage > 1
const canNavigateNext = currentChunkIndex < displayChunks.length - 1 || currentPage < totalPages

Expand Down Expand Up @@ -402,14 +401,14 @@ export function Document({
}
}, [isDirty, isCreatingNewChunk])

const handleUnsavedChangesOpenChange = useCallback((open: boolean) => {
const handleUnsavedChangesOpenChange = (open: boolean) => {
if (!open) {
setShowUnsavedChangesAlert(false)
setPendingAction(null)
}
}, [])
}

const handleDiscardChanges = useCallback(() => {
const handleDiscardChanges = () => {
setShowUnsavedChangesAlert(false)
const action = pendingAction
setPendingAction(null)
Expand All @@ -419,7 +418,7 @@ export function Document({
} else {
closeEditor()
}
}, [pendingAction, closeEditor])
}

const handleSaveEvent = useEffectEvent(handleSave)

Expand Down Expand Up @@ -646,7 +645,6 @@ export function Document({
if (found) {
setSelectedChunkId(chunkId)
} else if (!navigatedToNewPage && totalPagesRef.current > totalPages) {
// A new page was created — navigate to it
navigatedToNewPage = true
retries = 0
void goToPage(totalPagesRef.current)
Expand Down Expand Up @@ -681,10 +679,8 @@ export function Document({
}
: undefined

const enabledDisplayLabel = useMemo(() => {
if (enabledFilter.length === 0) return 'All'
return enabledFilter[0] === 'enabled' ? 'Enabled' : 'Disabled'
}, [enabledFilter])
const enabledDisplayLabel =
enabledFilter.length === 0 ? 'All' : enabledFilter[0] === 'enabled' ? 'Enabled' : 'Disabled'

const filterContent = useMemo(
() => (
Expand Down Expand Up @@ -724,7 +720,7 @@ export function Document({
)}
</div>
),
[enabledFilter, enabledDisplayLabel, setEnabledFilter]
[enabledFilter, setEnabledFilter]
)

const filterTags: FilterTag[] = useMemo(
Expand All @@ -746,31 +742,22 @@ export function Document({
[setSelectedChunkId]
)

const handleToggleEnabled = useCallback(
(chunkId: string) => {
const chunk = displayChunks.find((c) => c.id === chunkId)
if (!chunk) return
const handleToggleEnabled = (chunkId: string) => {
const chunk = displayChunks.find((c) => c.id === chunkId)
if (!chunk) return

const newEnabled = !chunk.enabled
updateChunk(chunkId, { enabled: newEnabled })
updateChunkMutation(
{ knowledgeBaseId, documentId, chunkId, enabled: newEnabled },
{ onError: () => updateChunk(chunkId, { enabled: chunk.enabled }) }
)
},
[displayChunks, knowledgeBaseId, documentId, updateChunk]
)
const newEnabled = !chunk.enabled
updateChunk(chunkId, { enabled: newEnabled })
updateChunkMutation(
{ knowledgeBaseId, documentId, chunkId, enabled: newEnabled },
{ onError: () => updateChunk(chunkId, { enabled: chunk.enabled }) }
)
}

const handleDeleteChunk = useCallback(
(chunkId: string) => {
const chunk = displayChunks.find((c) => c.id === chunkId)
if (chunk) {
setChunkToDelete(chunk)
setIsDeleteModalOpen(true)
}
},
[displayChunks]
)
const handleDeleteChunk = (chunkId: string) => {
const chunk = displayChunks.find((c) => c.id === chunkId)
if (chunk) setChunkToDelete(chunk)
}

const handleCloseDeleteModal = () => {
if (chunkToDelete) {
Expand All @@ -780,7 +767,6 @@ export function Document({
return newSet
})
}
setIsDeleteModalOpen(false)
setChunkToDelete(null)
}

Expand Down Expand Up @@ -863,17 +849,14 @@ export function Document({
performBulkChunkOperation('delete', chunksToDelete)
}

const [enabledCount, disabledCount] = useMemo(() => {
let enabled = 0
let disabled = 0
for (const chunk of displayChunks) {
if (selectedChunks.has(chunk.id)) {
if (chunk.enabled) enabled++
else disabled++
}
let enabledCount = 0
let disabledCount = 0
for (const chunk of displayChunks) {
if (selectedChunks.has(chunk.id)) {
if (chunk.enabled) enabledCount++
else disabledCount++
}
return [enabled, disabled]
}, [displayChunks, selectedChunks])
}

const isAllSelected = displayChunks.length > 0 && selectedChunks.size === displayChunks.length

Expand All @@ -890,7 +873,7 @@ export function Document({
}
}

setContextMenuChunk(chunk)
setContextMenuChunkId(chunk.id)
baseHandleContextMenu(e)
},
[
Expand All @@ -902,18 +885,15 @@ export function Document({
]
)

const handleEmptyContextMenu = useCallback(
(e: React.MouseEvent) => {
setContextMenuChunk(null)
baseHandleContextMenu(e)
},
[baseHandleContextMenu]
)
const handleEmptyContextMenu = (e: React.MouseEvent) => {
setContextMenuChunkId(null)
baseHandleContextMenu(e)
}

const handleContextMenuClose = useCallback(() => {
const handleContextMenuClose = () => {
closeContextMenu()
setContextMenuChunk(null)
}, [closeContextMenu])
setContextMenuChunkId(null)
}

const selectableConfig: SelectableConfig | undefined = isCompleted
? {
Expand Down Expand Up @@ -955,7 +935,17 @@ export function Document({
[activeSort, onSortColumn, onClearSort, goToPage]
)

const hasDocumentData = documentData !== null
const processingStatus = documentData?.processingStatus

const chunkRows: ResourceRow[] = useMemo(() => {
/**
* No document yet is "not known", not "not ready". Falling through to the status row
* flashed `Document not ready` on every open, for the frame between mount and the
* document query resolving — a claim about a document nothing had read yet.
*/
if (!hasDocumentData) return []

if (!isCompleted) {
return [
{
Expand All @@ -966,12 +956,10 @@ export function Document({
<div className='flex items-center gap-2'>
<FileText className='size-5 flex-shrink-0 text-[var(--text-muted)]' />
<span className='text-[var(--text-muted)] text-sm italic'>
{documentData?.processingStatus === 'pending' &&
'Document processing pending...'}
{documentData?.processingStatus === 'processing' &&
'Document processing in progress...'}
{documentData?.processingStatus === 'failed' && 'Document processing failed'}
{!documentData?.processingStatus && 'Document not ready'}
{processingStatus === 'pending' && 'Document processing pending...'}
{processingStatus === 'processing' && 'Document processing in progress...'}
{processingStatus === 'failed' && 'Document processing failed'}
{!processingStatus && 'Document not ready'}
</span>
</div>
),
Expand All @@ -992,16 +980,14 @@ export function Document({
cells: {
content: {
content: (
<span className='block truncate text-[var(--text-primary)] text-sm'>
<span className={cn('block', chipContentLabelClass)}>
<SearchHighlight text={previewContent} searchQuery={searchQuery} />
</span>
),
},
index: {
content: (
<span className='font-mono text-[var(--text-primary)] text-sm'>
{chunk.chunkIndex}
</span>
<span className={cn('font-mono', chipContentLabelClass)}>{chunk.chunkIndex}</span>
),
},
tokens: {
Expand All @@ -1017,7 +1003,7 @@ export function Document({
},
}
})
}, [isCompleted, documentData?.processingStatus, displayChunks, searchQuery])
}, [isCompleted, hasDocumentData, processingStatus, displayChunks, searchQuery])

const saveLabel =
saveStatus === 'saving'
Expand Down Expand Up @@ -1232,7 +1218,7 @@ export function Document({
chunk={chunkToDelete}
knowledgeBaseId={knowledgeBaseId}
documentId={documentId}
isOpen={isDeleteModalOpen}
isOpen={chunkToDelete !== null}
onClose={handleCloseDeleteModal}
/>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client'

import { Plus } from '@sim/emcn'
import { Database, FileText } from '@sim/emcn/icons'
import { Database, FileText, Plus } from '@sim/emcn/icons'
import { noop } from '@sim/utils/helpers'
import {
type BreadcrumbItem,
Expand Down
Loading
Loading