Skip to content
32 changes: 1 addition & 31 deletions apps/sim/app/workspace/[workspaceId]/files/[fileId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,9 @@
import type { Metadata } from 'next'
import { redirect } from 'next/navigation'
import { getSession } from '@/lib/auth'
import { verifyWorkspaceMembership } from '@/app/api/workflows/utils'
import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
import { Files } from '../files'

export const metadata: Metadata = {
title: 'Files',
robots: { index: false },
}

interface FileDetailPageProps {
params: Promise<{
workspaceId: string
fileId: string
}>
}

export default async function FileDetailPage({ params }: FileDetailPageProps) {
const { workspaceId } = await params
const session = await getSession()

if (!session?.user?.id) {
redirect('/')
}

const hasPermission = await verifyWorkspaceMembership(session.user.id, workspaceId)
if (!hasPermission) {
redirect('/')
}

const permissionConfig = await getUserPermissionConfig(session.user.id)
if (permissionConfig?.hideFilesTab) {
redirect(`/workspace/${workspaceId}`)
}

return <Files />
}
export default Files
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
'use client'

import { createLogger } from '@sim/logger'
import type { WorkspaceFileRecord } from '@/lib/uploads/contexts/workspace'
import { useParams } from 'next/navigation'
import { useWorkspaceFileRecord } from '@/hooks/queries/workspace-files'

const logger = createLogger('FileViewer')

interface FileViewerProps {
file: WorkspaceFileRecord
}
export function FileViewer() {
const params = useParams()
const workspaceId = params?.workspaceId as string
const fileId = params?.fileId as string

const { data: file, isLoading } = useWorkspaceFileRecord(workspaceId, fileId)

if (isLoading || !file) {
return null
}

export function FileViewer({ file }: FileViewerProps) {
const serveUrl = `/api/files/serve/${encodeURIComponent(file.key)}?context=workspace`

return (
Expand All @@ -18,7 +25,7 @@ export function FileViewer({ file }: FileViewerProps) {
src={serveUrl}
className='h-full w-full border-0'
title={file.name}
onError={(e) => {
onError={() => {
logger.error(`Failed to load file: ${file.name}`)
}}
/>
Expand Down
41 changes: 2 additions & 39 deletions apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/page.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,9 @@
import type { Metadata } from 'next'
import { redirect, unstable_rethrow } from 'next/navigation'
import { getSession } from '@/lib/auth'
import { getWorkspaceFile } from '@/lib/uploads/contexts/workspace'
import { verifyWorkspaceMembership } from '@/app/api/workflows/utils'
import { FileViewer } from '@/app/workspace/[workspaceId]/files/[fileId]/view/file-viewer'
import { FileViewer } from './file-viewer'

export const metadata: Metadata = {
title: 'File',
robots: { index: false },
}

interface FileViewerPageProps {
params: Promise<{
workspaceId: string
fileId: string
}>
}

export default async function FileViewerPage({ params }: FileViewerPageProps) {
const { workspaceId, fileId } = await params

const session = await getSession()
if (!session?.user?.id) {
redirect('/')
}

const hasPermission = await verifyWorkspaceMembership(session.user.id, workspaceId)
if (!hasPermission) {
redirect(`/workspace/${workspaceId}`)
}

let fileRecord: Awaited<ReturnType<typeof getWorkspaceFile>>
try {
fileRecord = await getWorkspaceFile(workspaceId, fileId)
} catch (error) {
unstable_rethrow(error)
redirect(`/workspace/${workspaceId}`)
}

if (!fileRecord) {
redirect(`/workspace/${workspaceId}`)
}

return <FileViewer file={fileRecord} />
}
export default FileViewer
8 changes: 8 additions & 0 deletions apps/sim/app/workspace/[workspaceId]/files/files.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import {
} from '@/hooks/queries/workspace-files'
import { useDebounce } from '@/hooks/use-debounce'
import { useInlineRename } from '@/hooks/use-inline-rename'
import { usePermissionConfig } from '@/hooks/use-permission-config'

type SaveStatus = 'idle' | 'saving' | 'saved' | 'error'

Expand Down Expand Up @@ -136,6 +137,13 @@ export function Files() {
const fileIdFromRoute =
typeof params?.fileId === 'string' && params.fileId.length > 0 ? params.fileId : null
const userPermissions = useUserPermissionsContext()
const { config: permissionConfig } = usePermissionConfig()

useEffect(() => {
if (permissionConfig.hideFilesTab) {
router.replace(`/workspace/${workspaceId}`)
}
}, [permissionConfig.hideFilesTab, router, workspaceId])

const { data: files = [], isLoading, error } = useWorkspaceFiles(workspaceId)
const { data: members } = useWorkspaceMembersQuery(workspaceId)
Expand Down
31 changes: 1 addition & 30 deletions apps/sim/app/workspace/[workspaceId]/files/page.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,9 @@
import type { Metadata } from 'next'
import { redirect } from 'next/navigation'
import { getSession } from '@/lib/auth'
import { verifyWorkspaceMembership } from '@/app/api/workflows/utils'
import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
import { Files } from './files'

export const metadata: Metadata = {
title: 'Files',
robots: { index: false },
}

interface FilesPageProps {
params: Promise<{
workspaceId: string
}>
}

export default async function FilesPage({ params }: FilesPageProps) {
const { workspaceId } = await params
const session = await getSession()

if (!session?.user?.id) {
redirect('/')
}

const hasPermission = await verifyWorkspaceMembership(session.user.id, workspaceId)
if (!hasPermission) {
redirect('/')
}

const permissionConfig = await getUserPermissionConfig(session.user.id)
if (permissionConfig?.hideFilesTab) {
redirect(`/workspace/${workspaceId}`)
}

return <Files />
}
export default Files
25 changes: 1 addition & 24 deletions apps/sim/app/workspace/[workspaceId]/home/page.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,8 @@
import type { Metadata } from 'next'
import { redirect } from 'next/navigation'
import { getSession } from '@/lib/auth'
import { verifyWorkspaceMembership } from '@/app/api/workflows/utils'
import { Home } from './home'

export const metadata: Metadata = {
title: 'Home',
}

interface HomePageProps {
params: Promise<{
workspaceId: string
}>
}

export default async function HomePage({ params }: HomePageProps) {
const { workspaceId } = await params
const session = await getSession()

if (!session?.user?.id) {
redirect('/')
}

const hasPermission = await verifyWorkspaceMembership(session.user.id, workspaceId)
if (!hasPermission) {
redirect('/')
}

return <Home key='home' />
}
export default Home
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,11 @@ export function ChunkEditor({
const [savedContent, setSavedContent] = useState(chunkContent)
const [tokenizerOn, setTokenizerOn] = useState(false)
const [hoveredTokenIndex, setHoveredTokenIndex] = useState<number | null>(null)
const prevChunkIdRef = useRef(chunk?.id)
const savedContentRef = useRef(chunkContent)

const editedContentRef = useRef(editedContent)
editedContentRef.current = editedContent

useEffect(() => {
if (isCreateMode) return
if (chunk?.id !== prevChunkIdRef.current) {
prevChunkIdRef.current = chunk?.id
savedContentRef.current = chunkContent
setSavedContent(chunkContent)
setEditedContent(chunkContent)
}
}, [isCreateMode, chunk?.id, chunkContent])

useEffect(() => {
if (isCreateMode || !chunk?.id) return
const controller = new AbortController()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { ChevronDown, ChevronUp, FileText, Pencil, Tag } from 'lucide-react'
import { useParams, useRouter, useSearchParams } from 'next/navigation'
Expand Down Expand Up @@ -47,6 +47,7 @@ import {
useUpdateChunk,
useUpdateDocument,
} from '@/hooks/queries/kb/knowledge'
import { useDebounce } from '@/hooks/use-debounce'
import { useInlineRename } from '@/hooks/use-inline-rename'

const logger = createLogger('Document')
Expand Down Expand Up @@ -152,7 +153,7 @@ export function Document({
const [showTagsModal, setShowTagsModal] = useState(false)

const [searchQuery, setSearchQuery] = useState('')
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('')
const debouncedSearchQuery = useDebounce(searchQuery, 200)
const [enabledFilter, setEnabledFilter] = useState<string[]>([])
const [activeSort, setActiveSort] = useState<{
column: string
Expand All @@ -168,11 +169,8 @@ export function Document({
chunks: initialChunks,
currentPage: initialPage,
totalPages: initialTotalPages,
hasNextPage: initialHasNextPage,
hasPrevPage: initialHasPrevPage,
goToPage: initialGoToPage,
error: initialError,
refreshChunks: initialRefreshChunks,
updateChunk: initialUpdateChunk,
isFetching: isFetchingChunks,
} = useDocumentChunks(
Expand Down Expand Up @@ -207,7 +205,9 @@ export function Document({
const [selectedChunks, setSelectedChunks] = useState<Set<string>>(() => new Set())

// Inline editor state
const [selectedChunkId, setSelectedChunkId] = useState<string | null>(null)
const [selectedChunkId, setSelectedChunkId] = useState<string | null>(() =>
searchParams.get('chunk')
)
const [isCreatingNewChunk, setIsCreatingNewChunk] = useState(false)
const [isDirty, setIsDirty] = useState(false)
const [saveStatus, setSaveStatus] = useState<SaveStatus>('idle')
Expand All @@ -217,27 +217,6 @@ export function Document({
const saveStatusRef = useRef<SaveStatus>('idle')
saveStatusRef.current = saveStatus

// Auto-select chunk from URL param on mount
const initialChunkParam = useRef(searchParams.get('chunk'))
useEffect(() => {
if (initialChunkParam.current) {
setSelectedChunkId(initialChunkParam.current)
initialChunkParam.current = null
}
}, [])

useEffect(() => {
const handler = setTimeout(() => {
startTransition(() => {
setDebouncedSearchQuery(searchQuery)
})
}, 200)

return () => {
clearTimeout(handler)
}
}, [searchQuery])

const isSearching = debouncedSearchQuery.trim().length > 0
const showingSearch = isSearching && searchQuery.trim().length > 0 && searchResults.length > 0
const SEARCH_PAGE_SIZE = 50
Expand All @@ -259,8 +238,6 @@ export function Document({

const currentPage = showingSearch ? searchCurrentPage : initialPage
const totalPages = showingSearch ? searchTotalPages : initialTotalPages
const hasNextPage = showingSearch ? searchCurrentPage < searchTotalPages : initialHasNextPage
const hasPrevPage = showingSearch ? searchCurrentPage > 1 : initialHasPrevPage

// Keep refs to displayChunks and totalPages so polling callbacks can read fresh data
const displayChunksRef = useRef(displayChunks)
Expand All @@ -281,12 +258,11 @@ export function Document({
if (showingSearch) {
return
}
return await initialGoToPage(page)
return initialGoToPage(page)
},
[showingSearch, initialGoToPage]
)

const refreshChunks = showingSearch ? async () => {} : initialRefreshChunks
const updateChunk = showingSearch
? (_id: string, _updates: Record<string, unknown>) => {}
: initialUpdateChunk
Expand All @@ -309,7 +285,6 @@ export function Document({
const {
isOpen: isContextMenuOpen,
position: contextMenuPosition,
menuRef,
handleContextMenu: baseHandleContextMenu,
closeMenu: closeContextMenu,
} = useContextMenu()
Expand Down Expand Up @@ -661,18 +636,11 @@ export function Document({
const chunk = displayChunks.find((c) => c.id === chunkId)
if (!chunk) return

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