Skip to content

Commit d00583f

Browse files
author
aadamgough
committed
added ability to edit knowledgebase name
1 parent a08d86d commit d00583f

File tree

1 file changed

+242
-8
lines changed
  • apps/sim/app/workspace/[workspaceId]/knowledge/[id]

1 file changed

+242
-8
lines changed

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx

Lines changed: 242 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useCallback, useEffect, useState } from 'react'
3+
import { useCallback, useEffect, useRef, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import { format } from 'date-fns'
66
import {
@@ -406,12 +406,22 @@ export function KnowledgeBase({
406406
}: KnowledgeBaseProps) {
407407
const params = useParams()
408408
const workspaceId = params.workspaceId as string
409-
const { removeKnowledgeBase } = useKnowledgeBasesList(workspaceId, { enabled: false })
409+
const { removeKnowledgeBase, refreshList } = useKnowledgeBasesList(workspaceId, {
410+
enabled: false,
411+
})
410412
const userPermissions = useUserPermissionsContext()
411413

412414
const [searchQuery, setSearchQuery] = useState('')
413415
const [showTagsModal, setShowTagsModal] = useState(false)
414416

417+
const [isEditingName, setIsEditingName] = useState(false)
418+
const [editName, setEditName] = useState('')
419+
const [isEditingDescription, setIsEditingDescription] = useState(false)
420+
const [editDescription, setEditDescription] = useState('')
421+
const [isSaving, setIsSaving] = useState(false)
422+
const nameInputRef = useRef<HTMLInputElement>(null)
423+
const descriptionInputRef = useRef<HTMLTextAreaElement>(null)
424+
415425
/**
416426
* Memoize the search query setter to prevent unnecessary re-renders
417427
*/
@@ -460,6 +470,168 @@ export function KnowledgeBase({
460470
const knowledgeBaseName = knowledgeBase?.name || passedKnowledgeBaseName || 'Knowledge Base'
461471
const error = knowledgeBaseError || documentsError
462472

473+
/**
474+
* Start editing the knowledge base name
475+
*/
476+
const handleStartEditName = useCallback(() => {
477+
setEditName(knowledgeBaseName)
478+
setIsEditingName(true)
479+
}, [knowledgeBaseName])
480+
481+
/**
482+
* Start editing the knowledge base description
483+
*/
484+
const handleStartEditDescription = useCallback(() => {
485+
setEditDescription(knowledgeBase?.description || '')
486+
setIsEditingDescription(true)
487+
}, [knowledgeBase?.description])
488+
489+
/**
490+
* Save the updated name
491+
*/
492+
const handleSaveName = useCallback(async () => {
493+
const trimmedName = editName.trim()
494+
495+
if (!trimmedName || trimmedName === knowledgeBaseName) {
496+
setIsEditingName(false)
497+
setEditName('')
498+
return
499+
}
500+
501+
try {
502+
setIsSaving(true)
503+
const response = await fetch(`/api/knowledge/${id}`, {
504+
method: 'PUT',
505+
headers: {
506+
'Content-Type': 'application/json',
507+
},
508+
body: JSON.stringify({ name: trimmedName }),
509+
})
510+
511+
if (!response.ok) {
512+
throw new Error('Failed to update knowledge base name')
513+
}
514+
515+
await refreshKnowledgeBase()
516+
await refreshList()
517+
setIsEditingName(false)
518+
setEditName('')
519+
logger.info(`Successfully updated knowledge base name to: ${trimmedName}`)
520+
} catch (err) {
521+
logger.error('Error updating knowledge base name:', err)
522+
setEditName(knowledgeBaseName)
523+
} finally {
524+
setIsSaving(false)
525+
}
526+
}, [editName, knowledgeBaseName, id, refreshKnowledgeBase, refreshList])
527+
528+
/**
529+
* Save the updated description
530+
*/
531+
const handleSaveDescription = useCallback(async () => {
532+
const trimmedDescription = editDescription.trim()
533+
const currentDescription = knowledgeBase?.description || ''
534+
535+
if (trimmedDescription === currentDescription) {
536+
setIsEditingDescription(false)
537+
setEditDescription('')
538+
return
539+
}
540+
541+
try {
542+
setIsSaving(true)
543+
const response = await fetch(`/api/knowledge/${id}`, {
544+
method: 'PUT',
545+
headers: {
546+
'Content-Type': 'application/json',
547+
},
548+
body: JSON.stringify({ description: trimmedDescription }),
549+
})
550+
551+
if (!response.ok) {
552+
throw new Error('Failed to update knowledge base description')
553+
}
554+
555+
await refreshKnowledgeBase()
556+
setIsEditingDescription(false)
557+
setEditDescription('')
558+
logger.info(`Successfully updated knowledge base description`)
559+
} catch (err) {
560+
logger.error('Error updating knowledge base description:', err)
561+
setEditDescription(knowledgeBase?.description || '')
562+
} finally {
563+
setIsSaving(false)
564+
}
565+
}, [editDescription, knowledgeBase?.description, id, refreshKnowledgeBase])
566+
567+
/**
568+
* Cancel editing name
569+
*/
570+
const handleCancelEditName = useCallback(() => {
571+
setIsEditingName(false)
572+
setEditName('')
573+
}, [])
574+
575+
/**
576+
* Cancel editing description
577+
*/
578+
const handleCancelEditDescription = useCallback(() => {
579+
setIsEditingDescription(false)
580+
setEditDescription('')
581+
}, [])
582+
583+
/**
584+
* Handle keyboard events for name input
585+
*/
586+
const handleNameKeyDown = useCallback(
587+
(e: React.KeyboardEvent) => {
588+
if (e.key === 'Enter') {
589+
e.preventDefault()
590+
handleSaveName()
591+
} else if (e.key === 'Escape') {
592+
e.preventDefault()
593+
handleCancelEditName()
594+
}
595+
},
596+
[handleSaveName, handleCancelEditName]
597+
)
598+
599+
/**
600+
* Handle keyboard events for description input
601+
*/
602+
const handleDescriptionKeyDown = useCallback(
603+
(e: React.KeyboardEvent) => {
604+
if (e.key === 'Enter' && !e.shiftKey) {
605+
e.preventDefault()
606+
handleSaveDescription()
607+
} else if (e.key === 'Escape') {
608+
e.preventDefault()
609+
handleCancelEditDescription()
610+
}
611+
},
612+
[handleSaveDescription, handleCancelEditDescription]
613+
)
614+
615+
/**
616+
* Focus and select name input when editing starts
617+
*/
618+
useEffect(() => {
619+
if (isEditingName && nameInputRef.current) {
620+
nameInputRef.current.focus()
621+
nameInputRef.current.select()
622+
}
623+
}, [isEditingName])
624+
625+
/**
626+
* Focus and select description input when editing starts
627+
*/
628+
useEffect(() => {
629+
if (isEditingDescription && descriptionInputRef.current) {
630+
descriptionInputRef.current.focus()
631+
descriptionInputRef.current.select()
632+
}
633+
}, [isEditingDescription])
634+
463635
const totalPages = Math.ceil(pagination.total / pagination.limit)
464636
const hasNextPage = currentPage < totalPages
465637
const hasPrevPage = currentPage > 1
@@ -991,9 +1163,37 @@ export function KnowledgeBase({
9911163
<Breadcrumb items={breadcrumbItems} />
9921164

9931165
<div className='mt-[14px] flex items-center justify-between'>
994-
<h1 className='font-medium text-[18px] text-[var(--text-primary)]'>
995-
{knowledgeBaseName}
996-
</h1>
1166+
{isEditingName ? (
1167+
<div className='relative inline-flex'>
1168+
<span
1169+
className='invisible whitespace-pre font-medium text-[18px]'
1170+
aria-hidden='true'
1171+
>
1172+
{editName || '\u00A0'}
1173+
</span>
1174+
<input
1175+
ref={nameInputRef}
1176+
value={editName}
1177+
onChange={(e) => setEditName(e.target.value)}
1178+
onKeyDown={handleNameKeyDown}
1179+
onBlur={handleSaveName}
1180+
className='absolute top-0 left-0 h-full w-full border-0 bg-transparent p-0 font-medium text-[18px] text-[var(--text-primary)] outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
1181+
maxLength={200}
1182+
disabled={isSaving}
1183+
autoComplete='off'
1184+
autoCorrect='off'
1185+
autoCapitalize='off'
1186+
spellCheck='false'
1187+
/>
1188+
</div>
1189+
) : (
1190+
<h1
1191+
className={`font-medium text-[18px] text-[var(--text-primary)] ${userPermissions.canEdit ? 'cursor-text' : ''}`}
1192+
onDoubleClick={userPermissions.canEdit ? handleStartEditName : undefined}
1193+
>
1194+
{knowledgeBaseName}
1195+
</h1>
1196+
)}
9971197
<div className='flex items-center gap-2'>
9981198
{userPermissions.canEdit && (
9991199
<Button
@@ -1023,11 +1223,45 @@ export function KnowledgeBase({
10231223
</div>
10241224
</div>
10251225

1026-
{knowledgeBase?.description && (
1027-
<p className='mt-[4px] line-clamp-2 max-w-[40vw] font-medium text-[14px] text-[var(--text-tertiary)]'>
1226+
{isEditingDescription ? (
1227+
<div className='relative mt-[4px] inline-flex max-w-[40vw]'>
1228+
<span
1229+
className='invisible whitespace-pre-wrap font-medium text-[14px]'
1230+
aria-hidden='true'
1231+
>
1232+
{editDescription || 'Add a description...'}
1233+
</span>
1234+
<textarea
1235+
ref={descriptionInputRef}
1236+
value={editDescription}
1237+
onChange={(e) => setEditDescription(e.target.value)}
1238+
onKeyDown={handleDescriptionKeyDown}
1239+
onBlur={handleSaveDescription}
1240+
className='absolute top-0 left-0 h-full w-full resize-none border-0 bg-transparent p-0 font-medium text-[14px] text-[var(--text-tertiary)] outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
1241+
maxLength={500}
1242+
disabled={isSaving}
1243+
autoComplete='off'
1244+
autoCorrect='off'
1245+
autoCapitalize='off'
1246+
spellCheck='false'
1247+
rows={2}
1248+
/>
1249+
</div>
1250+
) : knowledgeBase?.description ? (
1251+
<p
1252+
className={`mt-[4px] line-clamp-2 max-w-[40vw] font-medium text-[14px] text-[var(--text-tertiary)] ${userPermissions.canEdit ? 'cursor-text' : ''}`}
1253+
onDoubleClick={userPermissions.canEdit ? handleStartEditDescription : undefined}
1254+
>
10281255
{knowledgeBase.description}
10291256
</p>
1030-
)}
1257+
) : userPermissions.canEdit ? (
1258+
<p
1259+
className='mt-[4px] cursor-text font-medium text-[14px] text-[var(--text-muted)] transition-colors hover:text-[var(--text-tertiary)]'
1260+
onDoubleClick={handleStartEditDescription}
1261+
>
1262+
Add a description...
1263+
</p>
1264+
) : null}
10311265

10321266
<div className='mt-[16px] flex items-center gap-[8px]'>
10331267
<span className='text-[14px] text-[var(--text-muted)]'>

0 commit comments

Comments
 (0)