mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
feat(kb): added sort ordering to knowledgebase page, styling update (#1748)
* remove extraneous text from careers app * feat(kb): added sort order to kb * updated styles of workspace selector and delete button to match theme of rest of knowledgebase
This commit is contained in:
@@ -708,32 +708,30 @@ export function KnowledgeBase({
|
||||
<div className='flex-1 overflow-auto'>
|
||||
<div className='px-6 pb-6'>
|
||||
{/* Search and Filters Section */}
|
||||
<div className='mb-4 space-y-3 pt-1'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<SearchInput
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
placeholder='Search documents...'
|
||||
isLoading={isLoadingDocuments}
|
||||
/>
|
||||
<div className='mb-4 flex items-center justify-between pt-1'>
|
||||
<SearchInput
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
placeholder='Search documents...'
|
||||
isLoading={isLoadingDocuments}
|
||||
/>
|
||||
|
||||
<div className='flex items-center gap-3'>
|
||||
{/* Add Documents Button */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PrimaryButton
|
||||
onClick={handleAddDocuments}
|
||||
disabled={userPermissions.canEdit !== true}
|
||||
>
|
||||
<Plus className='h-3.5 w-3.5' />
|
||||
Add Documents
|
||||
</PrimaryButton>
|
||||
</TooltipTrigger>
|
||||
{userPermissions.canEdit !== true && (
|
||||
<TooltipContent>Write permission required to add documents</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
{/* Add Documents Button */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PrimaryButton
|
||||
onClick={handleAddDocuments}
|
||||
disabled={userPermissions.canEdit !== true}
|
||||
>
|
||||
<Plus className='h-3.5 w-3.5' />
|
||||
Add Documents
|
||||
</PrimaryButton>
|
||||
</TooltipTrigger>
|
||||
{userPermissions.canEdit !== true && (
|
||||
<TooltipContent>Write permission required to add documents</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,14 +10,65 @@ interface BaseOverviewProps {
|
||||
title: string
|
||||
docCount: number
|
||||
description: string
|
||||
createdAt?: string
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
export function BaseOverview({ id, title, docCount, description }: BaseOverviewProps) {
|
||||
function formatRelativeTime(dateString: string): string {
|
||||
const date = new Date(dateString)
|
||||
const now = new Date()
|
||||
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000)
|
||||
|
||||
if (diffInSeconds < 60) {
|
||||
return 'just now'
|
||||
}
|
||||
if (diffInSeconds < 3600) {
|
||||
const minutes = Math.floor(diffInSeconds / 60)
|
||||
return `${minutes}m ago`
|
||||
}
|
||||
if (diffInSeconds < 86400) {
|
||||
const hours = Math.floor(diffInSeconds / 3600)
|
||||
return `${hours}h ago`
|
||||
}
|
||||
if (diffInSeconds < 604800) {
|
||||
const days = Math.floor(diffInSeconds / 86400)
|
||||
return `${days}d ago`
|
||||
}
|
||||
if (diffInSeconds < 2592000) {
|
||||
const weeks = Math.floor(diffInSeconds / 604800)
|
||||
return `${weeks}w ago`
|
||||
}
|
||||
if (diffInSeconds < 31536000) {
|
||||
const months = Math.floor(diffInSeconds / 2592000)
|
||||
return `${months}mo ago`
|
||||
}
|
||||
const years = Math.floor(diffInSeconds / 31536000)
|
||||
return `${years}y ago`
|
||||
}
|
||||
|
||||
function formatAbsoluteDate(dateString: string): string {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
export function BaseOverview({
|
||||
id,
|
||||
title,
|
||||
docCount,
|
||||
description,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
}: BaseOverviewProps) {
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const params = useParams()
|
||||
const workspaceId = params?.workspaceId as string
|
||||
|
||||
// Create URL with knowledge base name as query parameter
|
||||
const searchParams = new URLSearchParams({
|
||||
kbName: title,
|
||||
})
|
||||
@@ -63,6 +114,23 @@ export function BaseOverview({ id, title, docCount, description }: BaseOverviewP
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timestamps */}
|
||||
{(createdAt || updatedAt) && (
|
||||
<div className='flex items-center gap-2 text-muted-foreground text-xs'>
|
||||
{updatedAt && (
|
||||
<span title={`Last updated: ${formatAbsoluteDate(updatedAt)}`}>
|
||||
Updated {formatRelativeTime(updatedAt)}
|
||||
</span>
|
||||
)}
|
||||
{updatedAt && createdAt && <span>•</span>}
|
||||
{createdAt && (
|
||||
<span title={`Created: ${formatAbsoluteDate(createdAt)}`}>
|
||||
Created {formatRelativeTime(createdAt)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className='line-clamp-2 overflow-hidden text-muted-foreground text-xs'>
|
||||
{description}
|
||||
</p>
|
||||
|
||||
@@ -10,6 +10,11 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { WorkspaceSelector } from '@/app/workspace/[workspaceId]/knowledge/components'
|
||||
import {
|
||||
commandListClass,
|
||||
dropdownContentClass,
|
||||
filterButtonClass,
|
||||
} from '@/app/workspace/[workspaceId]/knowledge/components/shared'
|
||||
|
||||
interface BreadcrumbItem {
|
||||
label: string
|
||||
@@ -24,8 +29,7 @@ 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 items-center justify-center gap-2',
|
||||
actionsContainer: 'flex items-center gap-2',
|
||||
} as const
|
||||
|
||||
interface KnowledgeHeaderOptions {
|
||||
@@ -66,42 +70,52 @@ export function KnowledgeHeader({ breadcrumbs, options }: KnowledgeHeaderProps)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Actions Area - always reserve consistent space */}
|
||||
<div className={HEADER_STYLES.actionsContainer}>
|
||||
{/* Workspace Selector */}
|
||||
{options?.knowledgeBaseId && (
|
||||
<WorkspaceSelector
|
||||
knowledgeBaseId={options.knowledgeBaseId}
|
||||
currentWorkspaceId={options.currentWorkspaceId || null}
|
||||
onWorkspaceChange={options.onWorkspaceChange}
|
||||
/>
|
||||
)}
|
||||
{/* Actions Area */}
|
||||
{options && (
|
||||
<div className={HEADER_STYLES.actionsContainer}>
|
||||
{/* Workspace Selector */}
|
||||
{options.knowledgeBaseId && (
|
||||
<WorkspaceSelector
|
||||
knowledgeBaseId={options.knowledgeBaseId}
|
||||
currentWorkspaceId={options.currentWorkspaceId || null}
|
||||
onWorkspaceChange={options.onWorkspaceChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Actions Menu */}
|
||||
{options?.onDeleteKnowledgeBase && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='h-8 w-8 p-0'
|
||||
aria-label='Knowledge base actions menu'
|
||||
{/* Actions Menu */}
|
||||
{options.onDeleteKnowledgeBase && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant='outline'
|
||||
size='sm'
|
||||
className={filterButtonClass}
|
||||
aria-label='Knowledge base actions menu'
|
||||
>
|
||||
<MoreHorizontal className='h-4 w-4' />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align='end'
|
||||
side='bottom'
|
||||
avoidCollisions={false}
|
||||
sideOffset={4}
|
||||
className={dropdownContentClass}
|
||||
>
|
||||
<MoreHorizontal className='h-4 w-4' />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align='end'>
|
||||
<DropdownMenuItem
|
||||
onClick={options.onDeleteKnowledgeBase}
|
||||
className='text-red-600 focus:text-red-600'
|
||||
>
|
||||
<Trash2 className='mr-2 h-4 w-4' />
|
||||
Delete Knowledge Base
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
<div className={`${commandListClass} py-1`}>
|
||||
<DropdownMenuItem
|
||||
onClick={options.onDeleteKnowledgeBase}
|
||||
className='flex cursor-pointer items-center gap-2 rounded-md px-3 py-2 font-[380] text-red-600 text-sm hover:bg-secondary/50 focus:bg-secondary/50 focus:text-red-600'
|
||||
>
|
||||
<Trash2 className='h-4 w-4' />
|
||||
Delete Knowledge Base
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export const filterButtonClass =
|
||||
'w-full justify-between rounded-[10px] border-[#E5E5E5] bg-[#FFFFFF] font-normal text-sm dark:border-[#414141] dark:bg-[var(--surface-elevated)]'
|
||||
|
||||
export const dropdownContentClass =
|
||||
'w-[220px] rounded-lg border-[#E5E5E5] bg-[#FFFFFF] p-0 shadow-xs dark:border-[#414141] dark:bg-[var(--surface-elevated)]'
|
||||
|
||||
export const commandListClass = 'overflow-y-auto overflow-x-hidden'
|
||||
|
||||
export type SortOption = 'name' | 'createdAt' | 'updatedAt' | 'docCount'
|
||||
export type SortOrder = 'asc' | 'desc'
|
||||
|
||||
export const SORT_OPTIONS = [
|
||||
{ value: 'updatedAt-desc', label: 'Last Updated' },
|
||||
{ value: 'createdAt-desc', label: 'Newest First' },
|
||||
{ value: 'createdAt-asc', label: 'Oldest First' },
|
||||
{ value: 'name-asc', label: 'Name (A-Z)' },
|
||||
{ value: 'name-desc', label: 'Name (Z-A)' },
|
||||
{ value: 'docCount-desc', label: 'Most Documents' },
|
||||
{ value: 'docCount-asc', label: 'Least Documents' },
|
||||
] as const
|
||||
@@ -11,6 +11,11 @@ import {
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import {
|
||||
commandListClass,
|
||||
dropdownContentClass,
|
||||
filterButtonClass,
|
||||
} from '@/app/workspace/[workspaceId]/knowledge/components/shared'
|
||||
import { useKnowledgeStore } from '@/stores/knowledge/store'
|
||||
|
||||
const logger = createLogger('WorkspaceSelector')
|
||||
@@ -132,53 +137,65 @@ export function WorkspaceSelector({
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
disabled={disabled || isLoading || isUpdating}
|
||||
className='h-8 gap-1 px-2 text-muted-foreground text-xs hover:text-foreground'
|
||||
className={filterButtonClass}
|
||||
>
|
||||
<span className='max-w-[120px] truncate'>
|
||||
<span className='truncate'>
|
||||
{isLoading
|
||||
? 'Loading...'
|
||||
: isUpdating
|
||||
? 'Updating...'
|
||||
: currentWorkspace?.name || 'No workspace'}
|
||||
</span>
|
||||
<ChevronDown className='h-3 w-3' />
|
||||
<ChevronDown className='ml-2 h-4 w-4 text-muted-foreground' />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align='end' className='w-48'>
|
||||
{/* No workspace option */}
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleWorkspaceChange(null)}
|
||||
className='flex items-center justify-between'
|
||||
>
|
||||
<span className='text-muted-foreground'>No workspace</span>
|
||||
{!currentWorkspaceId && <Check className='h-4 w-4' />}
|
||||
</DropdownMenuItem>
|
||||
|
||||
{/* Available workspaces */}
|
||||
{workspaces.map((workspace) => (
|
||||
<DropdownMenuContent
|
||||
align='end'
|
||||
side='bottom'
|
||||
avoidCollisions={false}
|
||||
sideOffset={4}
|
||||
className={dropdownContentClass}
|
||||
>
|
||||
<div className={`${commandListClass} py-1`}>
|
||||
{/* No workspace option */}
|
||||
<DropdownMenuItem
|
||||
key={workspace.id}
|
||||
onClick={() => handleWorkspaceChange(workspace.id)}
|
||||
className='flex items-center justify-between'
|
||||
onClick={() => handleWorkspaceChange(null)}
|
||||
className='flex cursor-pointer items-center justify-between rounded-md px-3 py-2 font-[380] text-card-foreground text-sm hover:bg-secondary/50 focus:bg-secondary/50'
|
||||
>
|
||||
<div className='flex flex-col'>
|
||||
<span>{workspace.name}</span>
|
||||
<span className='text-muted-foreground text-xs capitalize'>
|
||||
{workspace.permissions}
|
||||
</span>
|
||||
</div>
|
||||
{currentWorkspaceId === workspace.id && <Check className='h-4 w-4' />}
|
||||
<span className='text-muted-foreground'>No workspace</span>
|
||||
{!currentWorkspaceId && <Check className='h-4 w-4 text-muted-foreground' />}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
|
||||
{workspaces.length === 0 && !isLoading && (
|
||||
<DropdownMenuItem disabled>
|
||||
<span className='text-muted-foreground text-xs'>No workspaces with write access</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{/* Available workspaces */}
|
||||
{workspaces.map((workspace) => (
|
||||
<DropdownMenuItem
|
||||
key={workspace.id}
|
||||
onClick={() => handleWorkspaceChange(workspace.id)}
|
||||
className='flex cursor-pointer items-center justify-between rounded-md px-3 py-2 font-[380] text-card-foreground text-sm hover:bg-secondary/50 focus:bg-secondary/50'
|
||||
>
|
||||
<div className='flex flex-col'>
|
||||
<span>{workspace.name}</span>
|
||||
<span className='text-muted-foreground text-xs capitalize'>
|
||||
{workspace.permissions}
|
||||
</span>
|
||||
</div>
|
||||
{currentWorkspaceId === workspace.id && (
|
||||
<Check className='h-4 w-4 text-muted-foreground' />
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
|
||||
{workspaces.length === 0 && !isLoading && (
|
||||
<DropdownMenuItem disabled className='px-3 py-2'>
|
||||
<span className='text-muted-foreground text-xs'>
|
||||
No workspaces with write access
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo, useState } from 'react'
|
||||
import { LibraryBig, Plus } from 'lucide-react'
|
||||
import { Check, ChevronDown, LibraryBig, Plus } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import {
|
||||
BaseOverview,
|
||||
@@ -13,6 +21,18 @@ import {
|
||||
PrimaryButton,
|
||||
SearchInput,
|
||||
} from '@/app/workspace/[workspaceId]/knowledge/components'
|
||||
import {
|
||||
commandListClass,
|
||||
dropdownContentClass,
|
||||
filterButtonClass,
|
||||
SORT_OPTIONS,
|
||||
type SortOption,
|
||||
type SortOrder,
|
||||
} from '@/app/workspace/[workspaceId]/knowledge/components/shared'
|
||||
import {
|
||||
filterKnowledgeBases,
|
||||
sortKnowledgeBases,
|
||||
} from '@/app/workspace/[workspaceId]/knowledge/utils/sort'
|
||||
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
|
||||
import { useKnowledgeBasesList } from '@/hooks/use-knowledge'
|
||||
import type { KnowledgeBaseData } from '@/stores/knowledge/store'
|
||||
@@ -31,6 +51,18 @@ export function Knowledge() {
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)
|
||||
const [sortBy, setSortBy] = useState<SortOption>('updatedAt')
|
||||
const [sortOrder, setSortOrder] = useState<SortOrder>('desc')
|
||||
|
||||
const currentSortValue = `${sortBy}-${sortOrder}`
|
||||
const currentSortLabel =
|
||||
SORT_OPTIONS.find((opt) => opt.value === currentSortValue)?.label || 'Last Updated'
|
||||
|
||||
const handleSortChange = (value: string) => {
|
||||
const [field, order] = value.split('-') as [SortOption, SortOrder]
|
||||
setSortBy(field)
|
||||
setSortOrder(order)
|
||||
}
|
||||
|
||||
const handleKnowledgeBaseCreated = (newKnowledgeBase: KnowledgeBaseData) => {
|
||||
addKnowledgeBase(newKnowledgeBase)
|
||||
@@ -40,20 +72,18 @@ export function Knowledge() {
|
||||
refreshList()
|
||||
}
|
||||
|
||||
const filteredKnowledgeBases = useMemo(() => {
|
||||
if (!searchQuery.trim()) return knowledgeBases
|
||||
|
||||
const query = searchQuery.toLowerCase()
|
||||
return knowledgeBases.filter(
|
||||
(kb) => kb.name.toLowerCase().includes(query) || kb.description?.toLowerCase().includes(query)
|
||||
)
|
||||
}, [knowledgeBases, searchQuery])
|
||||
const filteredAndSortedKnowledgeBases = useMemo(() => {
|
||||
const filtered = filterKnowledgeBases(knowledgeBases, searchQuery)
|
||||
return sortKnowledgeBases(filtered, sortBy, sortOrder)
|
||||
}, [knowledgeBases, searchQuery, sortBy, sortOrder])
|
||||
|
||||
const formatKnowledgeBaseForDisplay = (kb: KnowledgeBaseWithDocCount) => ({
|
||||
id: kb.id,
|
||||
title: kb.name,
|
||||
docCount: kb.docCount || 0,
|
||||
description: kb.description || 'No description provided',
|
||||
createdAt: kb.createdAt,
|
||||
updatedAt: kb.updatedAt,
|
||||
})
|
||||
|
||||
const breadcrumbs = [{ id: 'knowledge', label: 'Knowledge' }]
|
||||
@@ -77,22 +107,59 @@ export function Knowledge() {
|
||||
placeholder='Search knowledge bases...'
|
||||
/>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PrimaryButton
|
||||
onClick={() => setIsCreateModalOpen(true)}
|
||||
disabled={userPermissions.canEdit !== true}
|
||||
<div className='flex items-center gap-2'>
|
||||
{/* Sort Dropdown */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant='outline' size='sm' className={filterButtonClass}>
|
||||
{currentSortLabel}
|
||||
<ChevronDown className='ml-2 h-4 w-4 text-muted-foreground' />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align='end'
|
||||
side='bottom'
|
||||
avoidCollisions={false}
|
||||
sideOffset={4}
|
||||
className={dropdownContentClass}
|
||||
>
|
||||
<Plus className='h-3.5 w-3.5' />
|
||||
<span>Create</span>
|
||||
</PrimaryButton>
|
||||
</TooltipTrigger>
|
||||
{userPermissions.canEdit !== true && (
|
||||
<TooltipContent>
|
||||
Write permission required to create knowledge bases
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
<div className={`${commandListClass} py-1`}>
|
||||
{SORT_OPTIONS.map((option, index) => (
|
||||
<div key={option.value}>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => handleSortChange(option.value)}
|
||||
className='flex cursor-pointer items-center justify-between rounded-md px-3 py-2 font-[380] text-card-foreground text-sm hover:bg-secondary/50 focus:bg-secondary/50'
|
||||
>
|
||||
<span>{option.label}</span>
|
||||
{currentSortValue === option.value && (
|
||||
<Check className='h-4 w-4 text-muted-foreground' />
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
{index === 0 && <DropdownMenuSeparator />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
{/* Create Button */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PrimaryButton
|
||||
onClick={() => setIsCreateModalOpen(true)}
|
||||
disabled={userPermissions.canEdit !== true}
|
||||
>
|
||||
<Plus className='h-3.5 w-3.5' />
|
||||
<span>Create</span>
|
||||
</PrimaryButton>
|
||||
</TooltipTrigger>
|
||||
{userPermissions.canEdit !== true && (
|
||||
<TooltipContent>
|
||||
Write permission required to create knowledge bases
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error State */}
|
||||
@@ -113,7 +180,7 @@ export function Knowledge() {
|
||||
<KnowledgeBaseCardSkeletonGrid count={8} />
|
||||
) : (
|
||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'>
|
||||
{filteredKnowledgeBases.length === 0 ? (
|
||||
{filteredAndSortedKnowledgeBases.length === 0 ? (
|
||||
knowledgeBases.length === 0 ? (
|
||||
<EmptyStateCard
|
||||
title='Create your first knowledge base'
|
||||
@@ -142,7 +209,7 @@ export function Knowledge() {
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
filteredKnowledgeBases.map((kb) => {
|
||||
filteredAndSortedKnowledgeBases.map((kb) => {
|
||||
const displayData = formatKnowledgeBaseForDisplay(
|
||||
kb as KnowledgeBaseWithDocCount
|
||||
)
|
||||
@@ -153,6 +220,8 @@ export function Knowledge() {
|
||||
title={displayData.title}
|
||||
docCount={displayData.docCount}
|
||||
description={displayData.description}
|
||||
createdAt={displayData.createdAt}
|
||||
updatedAt={displayData.updatedAt}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
55
apps/sim/app/workspace/[workspaceId]/knowledge/utils/sort.ts
Normal file
55
apps/sim/app/workspace/[workspaceId]/knowledge/utils/sort.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { KnowledgeBaseData } from '@/stores/knowledge/store'
|
||||
import type { SortOption, SortOrder } from '../components/shared'
|
||||
|
||||
interface KnowledgeBaseWithDocCount extends KnowledgeBaseData {
|
||||
docCount?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort knowledge bases by the specified field and order
|
||||
*/
|
||||
export function sortKnowledgeBases(
|
||||
knowledgeBases: KnowledgeBaseData[],
|
||||
sortBy: SortOption,
|
||||
sortOrder: SortOrder
|
||||
): KnowledgeBaseData[] {
|
||||
return [...knowledgeBases].sort((a, b) => {
|
||||
let comparison = 0
|
||||
|
||||
switch (sortBy) {
|
||||
case 'name':
|
||||
comparison = a.name.localeCompare(b.name)
|
||||
break
|
||||
case 'createdAt':
|
||||
comparison = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
||||
break
|
||||
case 'updatedAt':
|
||||
comparison = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()
|
||||
break
|
||||
case 'docCount':
|
||||
comparison =
|
||||
((a as KnowledgeBaseWithDocCount).docCount || 0) -
|
||||
((b as KnowledgeBaseWithDocCount).docCount || 0)
|
||||
break
|
||||
}
|
||||
|
||||
return sortOrder === 'asc' ? comparison : -comparison
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter knowledge bases by search query
|
||||
*/
|
||||
export function filterKnowledgeBases(
|
||||
knowledgeBases: KnowledgeBaseData[],
|
||||
searchQuery: string
|
||||
): KnowledgeBaseData[] {
|
||||
if (!searchQuery.trim()) {
|
||||
return knowledgeBases
|
||||
}
|
||||
|
||||
const query = searchQuery.toLowerCase()
|
||||
return knowledgeBases.filter(
|
||||
(kb) => kb.name.toLowerCase().includes(query) || kb.description?.toLowerCase().includes(query)
|
||||
)
|
||||
}
|
||||
@@ -72,41 +72,6 @@ export const CareersConfirmationEmail = ({
|
||||
schedule an initial conversation.
|
||||
</Text>
|
||||
|
||||
<Section
|
||||
style={{
|
||||
marginTop: '24px',
|
||||
marginBottom: '24px',
|
||||
padding: '20px',
|
||||
backgroundColor: '#f9f9f9',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #e5e5e5',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
margin: '0 0 12px 0',
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold',
|
||||
color: '#333333',
|
||||
}}
|
||||
>
|
||||
What Happens Next?
|
||||
</Text>
|
||||
<ul
|
||||
style={{
|
||||
margin: '0',
|
||||
padding: '0 0 0 20px',
|
||||
fontSize: '14px',
|
||||
color: '#333333',
|
||||
lineHeight: '1.8',
|
||||
}}
|
||||
>
|
||||
<li>Our team will review your application</li>
|
||||
<li>If you're a good fit, we'll reach out to schedule an interview</li>
|
||||
<li>We'll keep you updated throughout the process</li>
|
||||
</ul>
|
||||
</Section>
|
||||
|
||||
<Text style={baseStyles.paragraph}>
|
||||
In the meantime, feel free to explore our{' '}
|
||||
<a
|
||||
|
||||
Reference in New Issue
Block a user