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
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ export async function PUT(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

if (accessCheck.document?.connectorId) {
logger.warn(
`[${requestId}] User ${session.user.id} attempted to update chunk on connector-synced document: Doc=${documentId}`
)
return NextResponse.json(
{ error: 'Chunks from connector-synced documents are read-only' },
{ status: 403 }
)
}

const body = await req.json()

try {
Expand Down Expand Up @@ -167,6 +177,16 @@ export async function DELETE(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

if (accessCheck.document?.connectorId) {
logger.warn(
`[${requestId}] User ${session.user.id} attempted to delete chunk on connector-synced document: Doc=${documentId}`
)
return NextResponse.json(
{ error: 'Chunks from connector-synced documents are read-only' },
{ status: 403 }
)
}

await deleteChunk(chunkId, documentId, requestId)

logger.info(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@ export async function POST(
return NextResponse.json({ error: 'Document not found' }, { status: 404 })
}

if (doc.connectorId) {
logger.warn(
`[${requestId}] User ${userId} attempted to create chunk on connector-synced document: Doc=${documentId}`
)
return NextResponse.json(
{ error: 'Chunks from connector-synced documents are read-only' },
{ status: 403 }
)
}

// Allow manual chunk creation even if document is not fully processed
// but it should exist and not be in failed state
if (doc.processingStatus === 'failed') {
Expand Down Expand Up @@ -283,6 +293,16 @@ export async function PATCH(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

if (accessCheck.document?.connectorId) {
logger.warn(
`[${requestId}] User ${userId} attempted batch chunk operation on connector-synced document: Doc=${documentId}`
)
return NextResponse.json(
{ error: 'Chunks from connector-synced documents are read-only' },
{ status: 403 }
)
}

const body = await req.json()

try {
Expand Down
4 changes: 4 additions & 0 deletions apps/sim/app/api/knowledge/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export interface DocumentData {
boolean1?: boolean | null
boolean2?: boolean | null
boolean3?: boolean | null
// Connector fields
connectorId?: string | null
}

export interface EmbeddingData {
Expand Down Expand Up @@ -283,6 +285,8 @@ export async function checkDocumentWriteAccess(
boolean1: document.boolean1,
boolean2: document.boolean2,
boolean3: document.boolean3,
// Connector fields
connectorId: document.connectorId,
})
.from(document)
.where(and(eq(document.id, documentId), isNull(document.deletedAt)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ interface ChunkContextMenuProps {
* Whether add chunk is disabled
*/
disableAddChunk?: boolean
/**
* Whether the document is synced from a connector (chunks are read-only)
*/
isConnectorDocument?: boolean
/**
* Number of selected chunks (for batch operations)
*/
Expand Down Expand Up @@ -80,6 +84,7 @@ export function ChunkContextMenu({
disableToggleEnabled = false,
disableDelete = false,
disableAddChunk = false,
isConnectorDocument = false,
selectedCount = 1,
enabledCount = 0,
disabledCount = 0,
Expand Down Expand Up @@ -134,7 +139,7 @@ export function ChunkContextMenu({
onClose()
}}
>
Edit
{isConnectorDocument ? 'View' : 'Edit'}
</PopoverItem>
)}
{!isMultiSelect && onCopyContent && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export function EditChunkModal({
maxChunkSize,
}: EditChunkModalProps) {
const userPermissions = useUserPermissionsContext()
const isConnectorDocument = Boolean(document?.connectorId)
const canEditChunk = userPermissions.canEdit && !isConnectorDocument
const {
mutate: updateChunk,
isPending: isSaving,
Expand Down Expand Up @@ -186,7 +188,9 @@ export function EditChunkModal({
<ModalContent size='lg'>
<ModalHeader>
<div className='flex items-center gap-[8px]'>
<span>Edit Chunk #{chunk.chunkIndex}</span>
<span>
{canEditChunk ? 'Edit' : 'View'} Chunk #{chunk.chunkIndex}
</span>
{/* Navigation Controls */}
<div className='flex items-center gap-[6px]'>
<Tooltip.Root>
Expand Down Expand Up @@ -270,11 +274,15 @@ export function EditChunkModal({
value={editedContent}
onChange={(e) => setEditedContent(e.target.value)}
placeholder={
userPermissions.canEdit ? 'Enter chunk content...' : 'Read-only view'
canEditChunk
? 'Enter chunk content...'
: isConnectorDocument
? 'This chunk is synced from a connector and cannot be edited'
: 'Read-only view'
}
rows={20}
disabled={isSaving || isNavigating || !userPermissions.canEdit}
readOnly={!userPermissions.canEdit}
disabled={isSaving || isNavigating || !canEditChunk}
readOnly={!canEditChunk}
/>
)}
</div>
Expand Down Expand Up @@ -306,7 +314,7 @@ export function EditChunkModal({
>
Cancel
</Button>
{userPermissions.canEdit && (
{canEditChunk && (
<Button
variant='tertiary'
onClick={handleSaveContent}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ export function Document({

const combinedError = documentError || searchError || initialError

const isConnectorDocument = Boolean(documentData?.connectorId)
const effectiveKnowledgeBaseName = knowledgeBase?.name || knowledgeBaseName || 'Knowledge Base'
const effectiveDocumentName = documentData?.filename || documentName || 'Document'

Expand Down Expand Up @@ -781,7 +782,9 @@ export function Document({
<Button
onClick={() => setIsCreateChunkModalOpen(true)}
disabled={
documentData?.processingStatus === 'failed' || !userPermissions.canEdit
documentData?.processingStatus === 'failed' ||
!userPermissions.canEdit ||
isConnectorDocument
}
variant='tertiary'
className='h-[32px] rounded-[6px]'
Expand All @@ -792,6 +795,11 @@ export function Document({
{!userPermissions.canEdit && (
<Tooltip.Content>Write permission required to create chunks</Tooltip.Content>
)}
{userPermissions.canEdit && isConnectorDocument && (
<Tooltip.Content>
Chunks from connector-synced documents are read-only
</Tooltip.Content>
)}
</Tooltip.Root>
</div>
</div>
Expand Down Expand Up @@ -830,7 +838,8 @@ export function Document({
onCheckedChange={handleSelectAll}
disabled={
documentData?.processingStatus !== 'completed' ||
!userPermissions.canEdit
!userPermissions.canEdit ||
isConnectorDocument
}
aria-label='Select all chunks'
/>
Expand Down Expand Up @@ -917,7 +926,7 @@ export function Document({
onCheckedChange={(checked) =>
handleSelectChunk(chunk.id, checked as boolean)
}
disabled={!userPermissions.canEdit}
disabled={!userPermissions.canEdit || isConnectorDocument}
aria-label={`Select chunk ${chunk.chunkIndex}`}
onClick={(e) => e.stopPropagation()}
/>
Expand Down Expand Up @@ -957,7 +966,7 @@ export function Document({
e.stopPropagation()
handleToggleEnabled(chunk.id)
}}
disabled={!userPermissions.canEdit}
disabled={!userPermissions.canEdit || isConnectorDocument}
className='h-[28px] w-[28px] p-0 text-[var(--text-muted)] hover:text-[var(--text-primary)] disabled:opacity-50'
>
{chunk.enabled ? (
Expand All @@ -970,9 +979,11 @@ export function Document({
<Tooltip.Content side='top'>
{!userPermissions.canEdit
? 'Write permission required to modify chunks'
: chunk.enabled
? 'Disable Chunk'
: 'Enable Chunk'}
: isConnectorDocument
? 'Connector-synced chunks are read-only'
: chunk.enabled
? 'Disable Chunk'
: 'Enable Chunk'}
</Tooltip.Content>
</Tooltip.Root>
<Tooltip.Root>
Expand All @@ -983,7 +994,7 @@ export function Document({
e.stopPropagation()
handleDeleteChunk(chunk.id)
}}
disabled={!userPermissions.canEdit}
disabled={!userPermissions.canEdit || isConnectorDocument}
className='h-[28px] w-[28px] p-0 text-[var(--text-muted)] hover:text-[var(--text-error)] disabled:opacity-50'
>
<Trash className='h-[14px] w-[14px]' />
Expand All @@ -992,7 +1003,9 @@ export function Document({
<Tooltip.Content side='top'>
{!userPermissions.canEdit
? 'Write permission required to delete chunks'
: 'Delete Chunk'}
: isConnectorDocument
? 'Connector-synced chunks are read-only'
: 'Delete Chunk'}
</Tooltip.Content>
</Tooltip.Root>
</div>
Expand Down Expand Up @@ -1114,9 +1127,9 @@ export function Document({
{/* Bulk Action Bar */}
<ActionBar
selectedCount={selectedChunks.size}
onEnable={disabledCount > 0 ? handleBulkEnable : undefined}
onDisable={enabledCount > 0 ? handleBulkDisable : undefined}
onDelete={handleBulkDelete}
onEnable={disabledCount > 0 && !isConnectorDocument ? handleBulkEnable : undefined}
onDisable={enabledCount > 0 && !isConnectorDocument ? handleBulkDisable : undefined}
onDelete={!isConnectorDocument ? handleBulkDelete : undefined}
enabledCount={enabledCount}
disabledCount={disabledCount}
isLoading={isBulkOperating}
Expand Down Expand Up @@ -1197,7 +1210,7 @@ export function Document({
: undefined
}
onToggleEnabled={
contextMenuChunk && userPermissions.canEdit
contextMenuChunk && userPermissions.canEdit && !isConnectorDocument
? selectedChunks.size > 1
? () => {
if (disabledCount > 0) {
Expand All @@ -1210,20 +1223,27 @@ export function Document({
: undefined
}
onDelete={
contextMenuChunk && userPermissions.canEdit
contextMenuChunk && userPermissions.canEdit && !isConnectorDocument
? selectedChunks.size > 1
? handleBulkDelete
: () => handleDeleteChunk(contextMenuChunk.id)
: undefined
}
onAddChunk={
userPermissions.canEdit && documentData?.processingStatus !== 'failed'
userPermissions.canEdit &&
documentData?.processingStatus !== 'failed' &&
!isConnectorDocument
? () => setIsCreateChunkModalOpen(true)
: undefined
}
disableToggleEnabled={!userPermissions.canEdit}
disableDelete={!userPermissions.canEdit}
disableAddChunk={!userPermissions.canEdit || documentData?.processingStatus === 'failed'}
disableToggleEnabled={!userPermissions.canEdit || isConnectorDocument}
disableDelete={!userPermissions.canEdit || isConnectorDocument}
disableAddChunk={
!userPermissions.canEdit ||
documentData?.processingStatus === 'failed' ||
isConnectorDocument
}
isConnectorDocument={isConnectorDocument}
/>
</div>
)
Expand Down