Skip to content

Commit 90650be

Browse files
author
Theodore Li
committed
Live update resources in resource main view
1 parent c090c82 commit 90650be

File tree

4 files changed

+267
-239
lines changed

4 files changed

+267
-239
lines changed

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,19 +123,20 @@ const RESOURCE_INVALIDATORS: Record<
123123
MothershipResourceType,
124124
(qc: QueryClient, workspaceId: string, resourceId: string) => void
125125
> = {
126-
table: (qc, wId, id) => {
127-
qc.invalidateQueries({ queryKey: tableKeys.list(wId) })
126+
table: (qc, _wId, id) => {
127+
qc.invalidateQueries({ queryKey: tableKeys.lists() })
128128
qc.invalidateQueries({ queryKey: tableKeys.detail(id) })
129129
},
130130
file: (qc, wId, id) => {
131-
qc.invalidateQueries({ queryKey: workspaceFilesKeys.list(wId) })
131+
qc.invalidateQueries({ queryKey: workspaceFilesKeys.lists() })
132132
qc.invalidateQueries({ queryKey: workspaceFilesKeys.content(wId, id) })
133+
qc.invalidateQueries({ queryKey: workspaceFilesKeys.storageInfo() })
133134
},
134-
workflow: (qc, wId) => {
135-
qc.invalidateQueries({ queryKey: workflowKeys.list(wId) })
135+
workflow: (qc, _wId) => {
136+
qc.invalidateQueries({ queryKey: workflowKeys.lists() })
136137
},
137-
knowledgebase: (qc, wId, id) => {
138-
qc.invalidateQueries({ queryKey: knowledgeKeys.list(wId) })
138+
knowledgebase: (qc, _wId, id) => {
139+
qc.invalidateQueries({ queryKey: knowledgeKeys.lists() })
139140
qc.invalidateQueries({ queryKey: knowledgeKeys.detail(id) })
140141
},
141142
}

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import {
88
reportManualRunToolStop,
99
} from '@/lib/copilot/client-sse/run-tool-execution'
1010
import { MOTHERSHIP_CHAT_API_PATH } from '@/lib/copilot/constants'
11+
import {
12+
extractResourcesFromToolResult,
13+
isResourceToolName,
14+
} from '@/lib/copilot/resource-extraction'
1115
import { VFS_DIR_TO_RESOURCE } from '@/lib/copilot/resource-types'
1216
import { isWorkflowToolName } from '@/lib/copilot/workflow-tools'
1317
import { getNextWorkflowColor } from '@/lib/workflows/colors'
@@ -621,7 +625,7 @@ export function useChat(
621625
calledBy: activeSubagent,
622626
},
623627
})
624-
if (name === 'read') {
628+
if (name === 'read' || isResourceToolName(name)) {
625629
const args = (data?.arguments ?? data?.input) as
626630
| Record<string, unknown>
627631
| undefined
@@ -720,6 +724,17 @@ export function useChat(
720724
})
721725
}
722726
}
727+
728+
if (tc.status === 'success' && isResourceToolName(tc.name)) {
729+
const resources = extractResourcesFromToolResult(
730+
tc.name,
731+
toolArgsMap.get(id) as Record<string, unknown> | undefined,
732+
tc.result?.output
733+
)
734+
for (const resource of resources) {
735+
invalidateResourceQueries(queryClient, workspaceId, resource.type, resource.id)
736+
}
737+
}
723738
}
724739

725740
break
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import type { MothershipResource, MothershipResourceType } from '@/lib/copilot/resource-types'
2+
3+
type ChatResource = MothershipResource
4+
type ResourceType = MothershipResourceType
5+
6+
const RESOURCE_TOOL_NAMES = new Set([
7+
'user_table',
8+
'workspace_file',
9+
'create_workflow',
10+
'edit_workflow',
11+
'function_execute',
12+
'knowledge_base',
13+
'knowledge',
14+
])
15+
16+
export function isResourceToolName(toolName: string): boolean {
17+
return RESOURCE_TOOL_NAMES.has(toolName)
18+
}
19+
20+
function asRecord(value: unknown): Record<string, unknown> {
21+
return value && typeof value === 'object' ? (value as Record<string, unknown>) : {}
22+
}
23+
24+
/**
25+
* Extracts resource descriptors from a tool execution result.
26+
* Returns one or more resources for tools that create/modify workspace entities.
27+
*/
28+
export function extractResourcesFromToolResult(
29+
toolName: string,
30+
params: Record<string, unknown> | undefined,
31+
output: unknown
32+
): ChatResource[] {
33+
if (!isResourceToolName(toolName)) return []
34+
35+
const result = asRecord(output)
36+
const data = asRecord(result.data)
37+
38+
switch (toolName) {
39+
case 'user_table': {
40+
if (result.tableId) {
41+
return [
42+
{
43+
type: 'table',
44+
id: result.tableId as string,
45+
title: (result.tableName as string) || 'Table',
46+
},
47+
]
48+
}
49+
if (result.fileId) {
50+
return [
51+
{
52+
type: 'file',
53+
id: result.fileId as string,
54+
title: (result.fileName as string) || 'File',
55+
},
56+
]
57+
}
58+
const table = asRecord(data.table)
59+
if (table.id) {
60+
return [{ type: 'table', id: table.id as string, title: (table.name as string) || 'Table' }]
61+
}
62+
const args = asRecord(params?.args)
63+
const tableId =
64+
(data.tableId as string) ?? (args.tableId as string) ?? (params?.tableId as string)
65+
if (tableId) {
66+
return [
67+
{ type: 'table', id: tableId as string, title: (data.tableName as string) || 'Table' },
68+
]
69+
}
70+
return []
71+
}
72+
73+
case 'workspace_file': {
74+
const file = asRecord(data.file)
75+
if (file.id) {
76+
return [{ type: 'file', id: file.id as string, title: (file.name as string) || 'File' }]
77+
}
78+
const fileId = (data.fileId as string) ?? (data.id as string)
79+
if (fileId) {
80+
const fileName = (data.fileName as string) || (data.name as string) || 'File'
81+
return [{ type: 'file', id: fileId, title: fileName }]
82+
}
83+
return []
84+
}
85+
86+
case 'function_execute': {
87+
if (result.tableId) {
88+
return [
89+
{
90+
type: 'table',
91+
id: result.tableId as string,
92+
title: (result.tableName as string) || 'Table',
93+
},
94+
]
95+
}
96+
if (result.fileId) {
97+
return [
98+
{
99+
type: 'file',
100+
id: result.fileId as string,
101+
title: (result.fileName as string) || 'File',
102+
},
103+
]
104+
}
105+
return []
106+
}
107+
108+
case 'create_workflow':
109+
case 'edit_workflow': {
110+
const workflowId =
111+
(result.workflowId as string) ??
112+
(data.workflowId as string) ??
113+
(params?.workflowId as string)
114+
if (workflowId) {
115+
const workflowName =
116+
(result.workflowName as string) ??
117+
(data.workflowName as string) ??
118+
(params?.workflowName as string) ??
119+
'Workflow'
120+
return [{ type: 'workflow', id: workflowId, title: workflowName }]
121+
}
122+
return []
123+
}
124+
125+
case 'knowledge_base': {
126+
const kbId =
127+
(data.id as string) ??
128+
(result.knowledgeBaseId as string) ??
129+
(data.knowledgeBaseId as string) ??
130+
(params?.knowledgeBaseId as string)
131+
if (kbId) {
132+
const kbName =
133+
(data.name as string) ?? (result.knowledgeBaseName as string) ?? 'Knowledge Base'
134+
return [{ type: 'knowledgebase', id: kbId, title: kbName }]
135+
}
136+
return []
137+
}
138+
139+
case 'knowledge': {
140+
const kbArray = data.knowledge_bases as Array<Record<string, unknown>> | undefined
141+
if (!Array.isArray(kbArray)) return []
142+
const resources: ChatResource[] = []
143+
for (const kb of kbArray) {
144+
const id = kb.id as string | undefined
145+
if (id) {
146+
resources.push({
147+
type: 'knowledgebase',
148+
id,
149+
title: (kb.name as string) || 'Knowledge Base',
150+
})
151+
}
152+
}
153+
return resources
154+
}
155+
156+
default:
157+
return []
158+
}
159+
}
160+
161+
const DELETE_CAPABLE_TOOL_RESOURCE_TYPE: Record<string, ResourceType> = {
162+
delete_workflow: 'workflow',
163+
workspace_file: 'file',
164+
user_table: 'table',
165+
knowledge_base: 'knowledgebase',
166+
}
167+
168+
export function hasDeleteCapability(toolName: string): boolean {
169+
return toolName in DELETE_CAPABLE_TOOL_RESOURCE_TYPE
170+
}
171+
172+
/**
173+
* Extracts resource descriptors from a tool execution result when the tool
174+
* performed a deletion. Returns one or more deleted resources for tools that
175+
* destroy workspace entities.
176+
*/
177+
export function extractDeletedResourcesFromToolResult(
178+
toolName: string,
179+
params: Record<string, unknown> | undefined,
180+
output: unknown
181+
): ChatResource[] {
182+
const resourceType = DELETE_CAPABLE_TOOL_RESOURCE_TYPE[toolName]
183+
if (!resourceType) return []
184+
185+
const result = asRecord(output)
186+
const data = asRecord(result.data)
187+
const args = asRecord(params?.args)
188+
const operation = (args.operation ?? params?.operation) as string | undefined
189+
190+
switch (toolName) {
191+
case 'delete_workflow': {
192+
const workflowId = (result.workflowId as string) ?? (params?.workflowId as string)
193+
if (workflowId && result.deleted) {
194+
return [
195+
{ type: resourceType, id: workflowId, title: (result.name as string) || 'Workflow' },
196+
]
197+
}
198+
return []
199+
}
200+
201+
case 'workspace_file': {
202+
if (operation !== 'delete') return []
203+
const fileId = (data.id as string) ?? (args.fileId as string)
204+
if (fileId) {
205+
return [{ type: resourceType, id: fileId, title: (data.name as string) || 'File' }]
206+
}
207+
return []
208+
}
209+
210+
case 'user_table': {
211+
if (operation !== 'delete') return []
212+
const tableId = (args.tableId as string) ?? (params?.tableId as string)
213+
if (tableId) {
214+
return [{ type: resourceType, id: tableId, title: 'Table' }]
215+
}
216+
return []
217+
}
218+
219+
case 'knowledge_base': {
220+
if (operation !== 'delete') return []
221+
const kbId = (data.id as string) ?? (args.knowledgeBaseId as string)
222+
if (kbId) {
223+
return [{ type: resourceType, id: kbId, title: (data.name as string) || 'Knowledge Base' }]
224+
}
225+
return []
226+
}
227+
228+
default:
229+
return []
230+
}
231+
}

0 commit comments

Comments
 (0)