diff --git a/apps/sim/app/w/knowledge/[id]/base.tsx b/apps/sim/app/w/knowledge/[id]/base.tsx
index 7f4a3bbd60..ea2c345582 100644
--- a/apps/sim/app/w/knowledge/[id]/base.tsx
+++ b/apps/sim/app/w/knowledge/[id]/base.tsx
@@ -649,6 +649,9 @@ export function KnowledgeBase({
Status
|
+
+ Status
+ |
Actions
@@ -718,6 +721,11 @@ export function KnowledgeBase({
—
+ {/* Processing column */}
+ |
+ —
+ |
+
{/* Status column */}
—
diff --git a/apps/sim/app/w/knowledge/[id]/components/knowledge-base-loading.tsx b/apps/sim/app/w/knowledge/[id]/components/knowledge-base-loading.tsx
index 29ed24ded9..f363b5a31c 100644
--- a/apps/sim/app/w/knowledge/[id]/components/knowledge-base-loading.tsx
+++ b/apps/sim/app/w/knowledge/[id]/components/knowledge-base-loading.tsx
@@ -1,8 +1,8 @@
'use client'
-import { LibraryBig, Search } from 'lucide-react'
-import Link from 'next/link'
+import { Search } from 'lucide-react'
import { useSidebarStore } from '@/stores/sidebar/store'
+import { KnowledgeHeader } from '../../components/knowledge-header/knowledge-header'
import { DocumentTableSkeleton } from '../../components/skeletons/table-skeleton'
interface KnowledgeBaseLoadingProps {
@@ -14,28 +14,29 @@ export function KnowledgeBaseLoading({ knowledgeBaseName }: KnowledgeBaseLoading
const isSidebarCollapsed =
mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover'
+ const breadcrumbs = [
+ {
+ id: 'knowledge-root',
+ label: 'Knowledge',
+ href: '/w/knowledge',
+ },
+ {
+ id: 'knowledge-base-loading',
+ label: knowledgeBaseName,
+ },
+ ]
+
return (
{/* Fixed Header with Breadcrumbs */}
-
-
-
- Knowledge
-
- /
- {knowledgeBaseName}
-
+
{/* Main Content */}
-
+
{/* Search and Create Section */}
@@ -51,13 +52,16 @@ export function KnowledgeBaseLoading({ knowledgeBaseName }: KnowledgeBaseLoading
- {/* */}
+
+ {/* Add Documents Button - disabled state */}
+
+
{/* Table container */}
diff --git a/apps/sim/app/w/knowledge/components/knowledge-header/knowledge-header.tsx b/apps/sim/app/w/knowledge/components/knowledge-header/knowledge-header.tsx
index 457501b336..aef4edaad1 100644
--- a/apps/sim/app/w/knowledge/components/knowledge-header/knowledge-header.tsx
+++ b/apps/sim/app/w/knowledge/components/knowledge-header/knowledge-header.tsx
@@ -23,6 +23,8 @@ const HEADER_STYLES = {
link: 'group flex items-center gap-2 font-medium text-sm transition-colors hover:text-muted-foreground',
label: 'font-medium text-sm',
separator: 'text-muted-foreground',
+ // Always reserve consistent space for actions area
+ actionsContainer: 'flex h-8 w-8 items-center justify-center',
} as const
interface KnowledgeHeaderOptions {
@@ -60,30 +62,32 @@ export function KnowledgeHeader({ breadcrumbs, options }: KnowledgeHeaderProps)
})}
- {/* Actions Menu - only show if onDeleteKnowledgeBase is provided */}
- {options?.onDeleteKnowledgeBase && (
-
-
-
-
-
-
-
- Delete Knowledge Base
-
-
-
- )}
+ {/* Actions Area - always reserve consistent space */}
+
+ {options?.onDeleteKnowledgeBase && (
+
+
+
+
+
+
+
+ Delete Knowledge Base
+
+
+
+ )}
+
)
}
diff --git a/apps/sim/app/w/knowledge/components/skeletons/table-skeleton.tsx b/apps/sim/app/w/knowledge/components/skeletons/table-skeleton.tsx
index e00e8ebec5..502f31c1c6 100644
--- a/apps/sim/app/w/knowledge/components/skeletons/table-skeleton.tsx
+++ b/apps/sim/app/w/knowledge/components/skeletons/table-skeleton.tsx
@@ -37,7 +37,12 @@ export function DocumentTableRowSkeleton({ isSidebarCollapsed }: { isSidebarColl
|
- {/* Status column */}
+ {/* Processing Status column */}
+
+
+ |
+
+ {/* Active Status column */}
|
@@ -47,6 +52,7 @@ export function DocumentTableRowSkeleton({ isSidebarCollapsed }: { isSidebarColl
@@ -106,17 +112,18 @@ export function DocumentTableSkeleton({
return (
{/* Table header - fixed */}
-
-
+
+
-
-
-
-
+
+
+
+
-
+
-
+
+
@@ -138,6 +145,9 @@ export function DocumentTableSkeleton({
|
Uploaded
|
+
+ Processing
+ |
Status
|
@@ -151,16 +161,17 @@ export function DocumentTableSkeleton({
{/* Table body - scrollable */}
-
+
-
-
-
-
+
+
+
+
-
+
-
+
+
{Array.from({ length: rowCount }).map((_, i) => (
diff --git a/apps/sim/app/w/knowledge/loading.tsx b/apps/sim/app/w/knowledge/loading.tsx
index b94f61739e..6c1053caad 100644
--- a/apps/sim/app/w/knowledge/loading.tsx
+++ b/apps/sim/app/w/knowledge/loading.tsx
@@ -1,7 +1,8 @@
'use client'
-import { LibraryBig, Plus, Search } from 'lucide-react'
+import { Plus, Search } from 'lucide-react'
import { useSidebarStore } from '@/stores/sidebar/store'
+import { KnowledgeHeader } from './components/knowledge-header/knowledge-header'
import { KnowledgeBaseCardSkeletonGrid } from './components/skeletons/knowledge-base-card-skeleton'
export default function KnowledgeLoading() {
@@ -9,44 +10,47 @@ export default function KnowledgeLoading() {
const isSidebarCollapsed =
mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover'
+ const breadcrumbs = [{ id: 'knowledge', label: 'Knowledge' }]
+
return (
- {/* Fixed Header */}
-
-
-
Knowledge
-
+ {/* Header */}
+
- {/* Main Content */}
-
-
- {/* Search and Create Section */}
-
-
-
-
-
+
+ {/* Main Content */}
+
+
+ {/* Search and Create Section */}
+
+
+
+
+ className='flex items-center gap-1 rounded-md bg-[#701FFC] px-3 py-[7px] font-[480] text-primary-foreground text-sm shadow-[0_0_0_0_#701FFC] transition-all duration-200 hover:bg-[#6518E6] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)] disabled:opacity-50'
+ >
+
+
Create
+
+
+ {/* Content Area */}
+
-
-
-
- {/* Content Area */}
-
diff --git a/apps/sim/lib/uploads/s3-client.ts b/apps/sim/lib/uploads/s3-client.ts
index 8c5acd41b5..ce3a2e544f 100644
--- a/apps/sim/lib/uploads/s3-client.ts
+++ b/apps/sim/lib/uploads/s3-client.ts
@@ -33,6 +33,27 @@ export function getS3Client(): S3Client {
return _s3Client
}
+/**
+ * Sanitize a filename for use in S3 metadata headers
+ * S3 metadata headers must contain only ASCII printable characters (0x20-0x7E)
+ * and cannot contain certain special characters
+ */
+export function sanitizeFilenameForMetadata(filename: string): string {
+ return (
+ filename
+ // Remove non-ASCII characters (keep only printable ASCII 0x20-0x7E)
+ .replace(/[^\x20-\x7E]/g, '')
+ // Remove characters that are problematic in HTTP headers
+ .replace(/["\\]/g, '')
+ // Replace multiple spaces with single space
+ .replace(/\s+/g, ' ')
+ // Trim whitespace
+ .trim() ||
+ // Provide fallback if completely sanitized
+ 'file'
+ )
+}
+
/**
* File information structure
*/
@@ -110,6 +131,12 @@ export async function uploadToS3(
const safeFileName = fileName.replace(/\s+/g, '-') // Replace spaces with hyphens
const uniqueKey = `${Date.now()}-${safeFileName}`
+ // Sanitize filename for S3 metadata (only allow ASCII printable characters)
+ const sanitizedOriginalName = fileName
+ .replace(/[^\x20-\x7E]/g, '') // Remove non-ASCII characters
+ .replace(/["\\]/g, '') // Remove quotes and backslashes
+ .trim()
+
const s3Client = getS3Client()
// Upload the file to S3
@@ -119,7 +146,7 @@ export async function uploadToS3(
Key: uniqueKey,
Body: file,
ContentType: contentType,
- // Add some useful metadata
+ // Add some useful metadata with sanitized values
Metadata: {
originalName: encodeURIComponent(fileName), // Encode filename to prevent invalid characters in HTTP headers
uploadedAt: new Date().toISOString(),
@@ -133,7 +160,7 @@ export async function uploadToS3(
return {
path: servePath,
key: uniqueKey,
- name: fileName,
+ name: fileName, // Return the actual original filename in the response
size: fileSize,
type: contentType,
}