Skip to content

Commit 8fd8b1a

Browse files
committed
improvement(mothership): chat history and stability
1 parent 917af6d commit 8fd8b1a

File tree

2 files changed

+78
-18
lines changed

2 files changed

+78
-18
lines changed

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

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type {
2626
import { SUBAGENT_LABELS } from '../types'
2727
import {
2828
extractFileResource,
29+
extractResourcesFromHistory,
2930
extractTableResource,
3031
extractWorkflowResource,
3132
RESOURCE_TOOL_NAMES,
@@ -184,6 +185,12 @@ export function useChat(workspaceId: string, initialChatId?: string): UseChatRet
184185
if (!chatHistory || appliedChatIdRef.current === chatHistory.id) return
185186
appliedChatIdRef.current = chatHistory.id
186187
setMessages(chatHistory.messages.map(mapStoredMessage))
188+
189+
const restored = extractResourcesFromHistory(chatHistory.messages)
190+
if (restored.length > 0) {
191+
setResources(restored)
192+
setActiveResourceId(restored[restored.length - 1].id)
193+
}
187194
}, [chatHistory])
188195

189196
const processSSEStream = useCallback(
@@ -194,6 +201,9 @@ export function useChat(workspaceId: string, initialChatId?: string): UseChatRet
194201
const toolMap = new Map<string, number>()
195202
let lastTableId: string | null = null
196203
let lastWorkflowId: string | null = null
204+
let runningText = ''
205+
206+
toolArgsMapRef.current.clear()
197207

198208
const ensureTextBlock = (): ContentBlock => {
199209
const last = blocks[blocks.length - 1]
@@ -204,13 +214,9 @@ export function useChat(workspaceId: string, initialChatId?: string): UseChatRet
204214
}
205215

206216
const flush = () => {
207-
const text = blocks
208-
.filter((b) => b.type === 'text')
209-
.map((b) => b.content ?? '')
210-
.join('')
211217
setMessages((prev) =>
212218
prev.map((m) =>
213-
m.id === assistantId ? { ...m, content: text, contentBlocks: [...blocks] } : m
219+
m.id === assistantId ? { ...m, content: runningText, contentBlocks: [...blocks] } : m
214220
)
215221
)
216222
}
@@ -267,6 +273,7 @@ export function useChat(workspaceId: string, initialChatId?: string): UseChatRet
267273
if (chunk) {
268274
const tb = ensureTextBlock()
269275
tb.content = (tb.content ?? '') + chunk
276+
runningText += chunk
270277
flush()
271278
}
272279
break
@@ -411,19 +418,22 @@ export function useChat(workspaceId: string, initialChatId?: string): UseChatRet
411418
[workspaceId, queryClient, addResource]
412419
)
413420

414-
const finalize = useCallback(() => {
415-
sendingRef.current = false
416-
setIsSending(false)
417-
abortControllerRef.current = null
418-
chatBottomRef.current?.scrollIntoView({ behavior: 'smooth' })
419-
421+
const invalidateChatQueries = useCallback(() => {
420422
const activeChatId = chatIdRef.current
421423
if (activeChatId) {
422424
queryClient.invalidateQueries({ queryKey: taskKeys.detail(activeChatId) })
423425
}
424426
queryClient.invalidateQueries({ queryKey: taskKeys.list(workspaceId) })
425427
}, [workspaceId, queryClient])
426428

429+
const finalize = useCallback(() => {
430+
sendingRef.current = false
431+
setIsSending(false)
432+
abortControllerRef.current = null
433+
chatBottomRef.current?.scrollIntoView({ behavior: 'smooth' })
434+
invalidateChatQueries()
435+
}, [invalidateChatQueries])
436+
427437
useEffect(() => {
428438
const activeStreamId = chatHistory?.activeStreamId
429439
if (!activeStreamId || !appliedChatIdRef.current || sendingRef.current) return
@@ -556,13 +566,8 @@ export function useChat(workspaceId: string, initialChatId?: string): UseChatRet
556566
abortControllerRef.current = null
557567
sendingRef.current = false
558568
setIsSending(false)
559-
560-
const activeChatId = chatIdRef.current
561-
if (activeChatId) {
562-
queryClient.invalidateQueries({ queryKey: taskKeys.detail(activeChatId) })
563-
}
564-
queryClient.invalidateQueries({ queryKey: taskKeys.list(workspaceId) })
565-
}, [workspaceId, queryClient])
569+
invalidateChatQueries()
570+
}, [invalidateChatQueries])
566571

567572
useEffect(() => {
568573
return () => {

apps/sim/app/workspace/[workspaceId]/home/utils.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { TaskStoredMessage } from '@/hooks/queries/tasks'
12
import type { MothershipResource, SSEPayload } from './types'
23

34
export const RESOURCE_TOOL_NAMES = new Set([
@@ -78,3 +79,57 @@ export function extractWorkflowResource(
7879

7980
return null
8081
}
82+
83+
const GENERIC_TITLES = new Set(['Table', 'File', 'Workflow'])
84+
85+
/**
86+
* Reconstructs the MothershipResource list from persisted tool calls.
87+
* Adapts each stored tool call into an SSEPayload so the existing
88+
* extract*Resource functions are reused with zero duplication.
89+
* Deduplicates by type+id while preserving insertion order.
90+
*/
91+
export function extractResourcesFromHistory(messages: TaskStoredMessage[]): MothershipResource[] {
92+
const resourceMap = new Map<string, MothershipResource>()
93+
let lastTableId: string | null = null
94+
let lastWorkflowId: string | null = null
95+
96+
for (const msg of messages) {
97+
if (!msg.toolCalls) continue
98+
99+
for (const tc of msg.toolCalls) {
100+
if (tc.status !== 'success' || !RESOURCE_TOOL_NAMES.has(tc.name)) continue
101+
102+
const payload: SSEPayload = {
103+
type: 'tool_result',
104+
result: tc.result as Record<string, unknown>,
105+
success: true,
106+
toolName: tc.name,
107+
}
108+
const args = tc.params as Record<string, unknown> | undefined
109+
110+
let resource: MothershipResource | null = null
111+
if (tc.name === 'user_table') {
112+
resource = extractTableResource(payload, args, lastTableId)
113+
if (resource) lastTableId = resource.id
114+
} else if (tc.name === 'workspace_file') {
115+
resource = extractFileResource(payload, args)
116+
} else if (tc.name === 'create_workflow' || tc.name === 'edit_workflow') {
117+
resource = extractWorkflowResource(payload, lastWorkflowId)
118+
if (resource) lastWorkflowId = resource.id
119+
}
120+
121+
if (resource) {
122+
const key = `${resource.type}:${resource.id}`
123+
const existing = resourceMap.get(key)
124+
if (
125+
!existing ||
126+
(GENERIC_TITLES.has(existing.title) && !GENERIC_TITLES.has(resource.title))
127+
) {
128+
resourceMap.set(key, resource)
129+
}
130+
}
131+
}
132+
}
133+
134+
return Array.from(resourceMap.values())
135+
}

0 commit comments

Comments
 (0)