Skip to content

Commit 28c8afc

Browse files
waleedlatif1claude
andcommitted
feat(mothership): inline rename for resource tabs + workspace_file rename tool
- Add double-click inline rename on file and table resource tabs - Wire useInlineRename + useRenameWorkspaceFile/useRenameTable mutations - Add rename operation to workspace_file copilot tool (schema, server, router) - Add knowledge base resource support (type, extraction, rendering, actions) - Accept optional className on InlineRenameInput for context-specific sizing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 511e3a9 commit 28c8afc

File tree

14 files changed

+257
-29
lines changed

14 files changed

+257
-29
lines changed

apps/sim/app/workspace/[workspaceId]/components/inline-rename-input.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
'use client'
22

33
import { useEffect, useRef } from 'react'
4+
import { cn } from '@/lib/core/utils/cn'
45

56
interface InlineRenameInputProps {
67
value: string
78
onChange: (value: string) => void
89
onSubmit: () => void
910
onCancel: () => void
11+
className?: string
1012
}
1113

12-
export function InlineRenameInput({ value, onChange, onSubmit, onCancel }: InlineRenameInputProps) {
14+
export function InlineRenameInput({
15+
value,
16+
onChange,
17+
onSubmit,
18+
onCancel,
19+
className,
20+
}: InlineRenameInputProps) {
1321
const inputRef = useRef<HTMLInputElement>(null)
1422

1523
useEffect(() => {
@@ -32,7 +40,10 @@ export function InlineRenameInput({ value, onChange, onSubmit, onCancel }: Inlin
3240
}}
3341
onBlur={onSubmit}
3442
onClick={(e) => e.stopPropagation()}
35-
className='min-w-0 flex-1 truncate border-0 bg-transparent p-0 font-medium text-[14px] text-[var(--text-body)] outline-none focus:outline-none focus:ring-0'
43+
className={cn(
44+
'min-w-0 flex-1 truncate border-0 bg-transparent p-0 font-medium text-[14px] text-[var(--text-body)] outline-none focus:outline-none focus:ring-0',
45+
className
46+
)}
3647
/>
3748
)
3849
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1-
export { EmbeddedWorkflowActions, ResourceContent } from './resource-content'
1+
export {
2+
EmbeddedKnowledgeBaseActions,
3+
EmbeddedWorkflowActions,
4+
ResourceContent,
5+
} from './resource-content'
26
export { ResourceTabs } from './resource-tabs'
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
export { EmbeddedWorkflowActions, ResourceContent } from './resource-content'
1+
export {
2+
EmbeddedKnowledgeBaseActions,
3+
EmbeddedWorkflowActions,
4+
ResourceContent,
5+
} from './resource-content'

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react'
44
import { Square } from 'lucide-react'
55
import { useRouter } from 'next/navigation'
66
import { Button, PlayOutline, Skeleton, Tooltip } from '@/components/emcn'
7+
import { BookOpen } from '@/components/emcn/icons'
78
import { WorkflowIcon } from '@/components/icons'
89
import {
910
FileViewer,
1011
type PreviewMode,
1112
} from '@/app/workspace/[workspaceId]/files/components/file-viewer'
1213
import type { MothershipResource } from '@/app/workspace/[workspaceId]/home/types'
14+
import { KnowledgeBase } from '@/app/workspace/[workspaceId]/knowledge/[id]/base'
1315
import { useWorkspacePermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
1416
import { Table } from '@/app/workspace/[workspaceId]/tables/[tableId]/components'
1517
import { useUsageLimits } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/hooks'
@@ -62,6 +64,16 @@ export function ResourceContent({ workspaceId, resource, previewMode }: Resource
6264
</Suspense>
6365
)
6466

67+
case 'knowledgebase':
68+
return (
69+
<KnowledgeBase
70+
key={resource.id}
71+
id={resource.id}
72+
knowledgeBaseName={resource.title}
73+
workspaceId={workspaceId}
74+
/>
75+
)
76+
6577
default:
6678
return null
6779
}
@@ -147,6 +159,40 @@ export function EmbeddedWorkflowActions({ workspaceId, workflowId }: EmbeddedWor
147159
)
148160
}
149161

162+
interface EmbeddedKnowledgeBaseActionsProps {
163+
workspaceId: string
164+
knowledgeBaseId: string
165+
}
166+
167+
export function EmbeddedKnowledgeBaseActions({
168+
workspaceId,
169+
knowledgeBaseId,
170+
}: EmbeddedKnowledgeBaseActionsProps) {
171+
const router = useRouter()
172+
173+
const handleOpenKnowledgeBase = useCallback(() => {
174+
router.push(`/workspace/${workspaceId}/knowledge/${knowledgeBaseId}`)
175+
}, [router, workspaceId, knowledgeBaseId])
176+
177+
return (
178+
<Tooltip.Root>
179+
<Tooltip.Trigger asChild>
180+
<Button
181+
variant='subtle'
182+
onClick={handleOpenKnowledgeBase}
183+
className='shrink-0 bg-transparent px-[8px] py-[5px] text-[12px]'
184+
aria-label='Open knowledge base'
185+
>
186+
<BookOpen className='h-[16px] w-[16px] text-[var(--text-icon)]' />
187+
</Button>
188+
</Tooltip.Trigger>
189+
<Tooltip.Content side='bottom'>
190+
<p>Open Knowledge Base</p>
191+
</Tooltip.Content>
192+
</Tooltip.Root>
193+
)
194+
}
195+
150196
interface EmbeddedFileProps {
151197
workspaceId: string
152198
fileId: string

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import {
88
useCallback,
99
} from 'react'
1010
import { Button, Tooltip } from '@/components/emcn'
11-
import { PanelLeft, Table as TableIcon } from '@/components/emcn/icons'
11+
import { BookOpen, PanelLeft, Table as TableIcon } from '@/components/emcn/icons'
1212
import { WorkflowIcon } from '@/components/icons'
1313
import { getDocumentIcon } from '@/components/icons/document-icons'
1414
import { cn } from '@/lib/core/utils/cn'
15+
import { InlineRenameInput } from '@/app/workspace/[workspaceId]/components/inline-rename-input'
1516
import type { PreviewMode } from '@/app/workspace/[workspaceId]/files/components/file-viewer'
1617
import type {
1718
MothershipResource,
@@ -47,6 +48,8 @@ function PreviewModeIcon({ mode, ...props }: { mode: PreviewMode } & SVGProps<SV
4748
)
4849
}
4950

51+
const RENAMABLE_TYPES = new Set(['file', 'table'])
52+
5053
interface ResourceTabsProps {
5154
resources: MothershipResource[]
5255
activeId: string | null
@@ -55,11 +58,18 @@ interface ResourceTabsProps {
5558
previewMode?: PreviewMode
5659
onCyclePreviewMode?: () => void
5760
actions?: ReactNode
61+
editingId?: string | null
62+
editValue?: string
63+
onEditValueChange?: (value: string) => void
64+
onStartRename?: (id: string, currentName: string) => void
65+
onSubmitRename?: () => void
66+
onCancelRename?: () => void
5867
}
5968

6069
const RESOURCE_ICONS: Record<Exclude<MothershipResourceType, 'file'>, ElementType> = {
6170
table: TableIcon,
6271
workflow: WorkflowIcon,
72+
knowledgebase: BookOpen,
6373
}
6474

6575
function getResourceIcon(resource: MothershipResource): ElementType {
@@ -81,6 +91,12 @@ export function ResourceTabs({
8191
previewMode,
8292
onCyclePreviewMode,
8393
actions,
94+
editingId,
95+
editValue,
96+
onEditValueChange,
97+
onStartRename,
98+
onSubmitRename,
99+
onCancelRename,
84100
}: ResourceTabsProps) {
85101
const scrollRef = useCallback<RefCallback<HTMLDivElement>>((node) => {
86102
if (!node) return
@@ -118,20 +134,37 @@ export function ResourceTabs({
118134
{resources.map((resource) => {
119135
const Icon = getResourceIcon(resource)
120136
const isActive = activeId === resource.id
137+
const isEditing = editingId === resource.id
138+
const isRenamable = RENAMABLE_TYPES.has(resource.type)
121139

122140
return (
123-
<Tooltip.Root key={resource.id}>
141+
<Tooltip.Root key={resource.id} open={isEditing ? false : undefined}>
124142
<Tooltip.Trigger asChild>
125143
<Button
126144
variant='subtle'
127145
onClick={() => onSelect(resource.id)}
146+
onDoubleClick={
147+
isRenamable && onStartRename
148+
? () => onStartRename(resource.id, resource.title)
149+
: undefined
150+
}
128151
className={cn(
129152
'shrink-0 bg-transparent px-[8px] py-[4px] text-[12px]',
130153
isActive && 'bg-[var(--surface-4)]'
131154
)}
132155
>
133156
<Icon className={cn('mr-[6px] h-[14px] w-[14px] text-[var(--text-icon)]')} />
134-
{resource.title}
157+
{isEditing && onEditValueChange && onSubmitRename && onCancelRename ? (
158+
<InlineRenameInput
159+
value={editValue ?? ''}
160+
onChange={onEditValueChange}
161+
onSubmit={onSubmitRename}
162+
onCancel={onCancelRename}
163+
className='min-w-[60px] max-w-[160px] text-[12px]'
164+
/>
165+
) : (
166+
resource.title
167+
)}
135168
</Button>
136169
</Tooltip.Trigger>
137170
<Tooltip.Content side='bottom'>

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
'use client'
22

3-
import { useCallback, useEffect, useState } from 'react'
3+
import { useCallback, useEffect, useRef, useState } from 'react'
44
import { cn } from '@/lib/core/utils/cn'
55
import { getFileExtension } from '@/lib/uploads/utils/file-utils'
66
import type { PreviewMode } from '@/app/workspace/[workspaceId]/files/components/file-viewer'
77
import type { MothershipResource } from '@/app/workspace/[workspaceId]/home/types'
8-
import { EmbeddedWorkflowActions, ResourceContent, ResourceTabs } from './components'
8+
import { useRenameTable } from '@/hooks/queries/tables'
9+
import { useRenameWorkspaceFile } from '@/hooks/queries/workspace-files'
10+
import { useInlineRename } from '@/hooks/use-inline-rename'
11+
import {
12+
EmbeddedKnowledgeBaseActions,
13+
EmbeddedWorkflowActions,
14+
ResourceContent,
15+
ResourceTabs,
16+
} from './components'
917

1018
const PREVIEWABLE_EXTENSIONS = new Set(['md', 'html', 'htm', 'csv'])
1119
const PREVIEW_ONLY_EXTENSIONS = new Set(['html', 'htm'])
@@ -21,13 +29,14 @@ interface MothershipViewProps {
2129
resources: MothershipResource[]
2230
activeResourceId: string | null
2331
onSelectResource: (id: string) => void
32+
onRenameResource: (id: string, newTitle: string) => void
2433
onCollapse: () => void
2534
isCollapsed: boolean
2635
className?: string
2736
}
2837

2938
/**
30-
* Split-pane view that renders embedded resources (tables, files, workflows)
39+
* Split-pane view that renders embedded resources (tables, files, workflows, knowledge bases)
3140
* alongside the chat conversation. Composes ResourceTabs for navigation
3241
* and ResourceContent for rendering the active resource.
3342
*/
@@ -36,12 +45,40 @@ export function MothershipView({
3645
resources,
3746
activeResourceId,
3847
onSelectResource,
48+
onRenameResource,
3949
onCollapse,
4050
isCollapsed,
4151
className,
4252
}: MothershipViewProps) {
4353
const active = resources.find((r) => r.id === activeResourceId) ?? resources[0] ?? null
4454

55+
const { mutate: renameFile } = useRenameWorkspaceFile()
56+
const { mutate: renameTable } = useRenameTable(workspaceId)
57+
58+
const resourcesRef = useRef(resources)
59+
useEffect(() => {
60+
resourcesRef.current = resources
61+
}, [resources])
62+
63+
const handleRenameSave = useCallback(
64+
(id: string, newName: string) => {
65+
const resource = resourcesRef.current.find((r) => r.id === id)
66+
if (!resource) return
67+
68+
onRenameResource(id, newName)
69+
70+
if (resource.type === 'file') {
71+
renameFile({ workspaceId, fileId: id, name: newName })
72+
} else if (resource.type === 'table') {
73+
renameTable({ tableId: id, name: newName })
74+
}
75+
},
76+
[onRenameResource, renameFile, renameTable, workspaceId]
77+
)
78+
79+
const { editingId, editValue, setEditValue, startRename, submitRename, cancelRename } =
80+
useInlineRename({ onSave: handleRenameSave })
81+
4582
const [previewMode, setPreviewMode] = useState<PreviewMode>('split')
4683
const handleCyclePreview = useCallback(() => setPreviewMode((m) => PREVIEW_CYCLE[m]), [])
4784

@@ -56,6 +93,8 @@ export function MothershipView({
5693
const headerActions =
5794
active?.type === 'workflow' ? (
5895
<EmbeddedWorkflowActions workspaceId={workspaceId} workflowId={active.id} />
96+
) : active?.type === 'knowledgebase' ? (
97+
<EmbeddedKnowledgeBaseActions workspaceId={workspaceId} knowledgeBaseId={active.id} />
5998
) : null
6099

61100
return (
@@ -75,6 +114,12 @@ export function MothershipView({
75114
actions={headerActions}
76115
previewMode={isActivePreviewable ? previewMode : undefined}
77116
onCyclePreviewMode={isActivePreviewable ? handleCyclePreview : undefined}
117+
editingId={editingId}
118+
editValue={editValue}
119+
onEditValueChange={setEditValue}
120+
onStartRename={startRename}
121+
onSubmitRename={submitRename}
122+
onCancelRename={cancelRename}
78123
/>
79124
<div className='min-h-0 flex-1 overflow-hidden'>
80125
{active && (

apps/sim/app/workspace/[workspaceId]/home/home.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ export function Home({ chatId }: HomeProps = {}) {
169169
resources,
170170
activeResourceId,
171171
setActiveResourceId,
172+
renameResource,
172173
} = useChat(workspaceId, chatId)
173174

174175
const [isResourceCollapsed, setIsResourceCollapsed] = useState(false)
@@ -346,6 +347,7 @@ export function Home({ chatId }: HomeProps = {}) {
346347
resources={resources}
347348
activeResourceId={activeResourceId}
348349
onSelectResource={setActiveResourceId}
350+
onRenameResource={renameResource}
349351
onCollapse={collapseResource}
350352
isCollapsed={isResourceCollapsed}
351353
className={animateResourcePanel ? 'animate-slide-in-right' : undefined}

0 commit comments

Comments
 (0)