Skip to content

Commit 8597786

Browse files
Sg312emir-karabeg
authored andcommitted
Persist and load chats properly
1 parent 1aada6b commit 8597786

File tree

4 files changed

+284
-228
lines changed

4 files changed

+284
-228
lines changed

apps/sim/app/api/copilot/chat/update-messages/route.ts

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,30 @@ const logger = createLogger('CopilotChatUpdateAPI')
1717
const UpdateMessagesSchema = z.object({
1818
chatId: z.string(),
1919
messages: z.array(
20-
z.object({
21-
id: z.string(),
22-
role: z.enum(['user', 'assistant']),
23-
content: z.string(),
24-
timestamp: z.string(),
25-
toolCalls: z.array(z.any()).optional(),
26-
contentBlocks: z.array(z.any()).optional(),
27-
fileAttachments: z
28-
.array(
29-
z.object({
30-
id: z.string(),
31-
key: z.string(),
32-
filename: z.string(),
33-
media_type: z.string(),
34-
size: z.number(),
35-
})
36-
)
37-
.optional(),
38-
})
20+
z
21+
.object({
22+
id: z.string(),
23+
role: z.enum(['user', 'assistant', 'system']),
24+
content: z.string(),
25+
timestamp: z.string(),
26+
toolCalls: z.array(z.any()).optional(),
27+
contentBlocks: z.array(z.any()).optional(),
28+
fileAttachments: z
29+
.array(
30+
z.object({
31+
id: z.string(),
32+
key: z.string(),
33+
filename: z.string(),
34+
media_type: z.string(),
35+
size: z.number(),
36+
})
37+
)
38+
.optional(),
39+
contexts: z.array(z.any()).optional(),
40+
citations: z.array(z.any()).optional(),
41+
errorType: z.string().optional(),
42+
})
43+
.passthrough() // Preserve any additional fields for future compatibility
3944
),
4045
planArtifact: z.string().nullable().optional(),
4146
config: z
@@ -57,6 +62,19 @@ export async function POST(req: NextRequest) {
5762
}
5863

5964
const body = await req.json()
65+
66+
// Debug: Log what we received
67+
const lastMsg = body.messages?.[body.messages.length - 1]
68+
if (lastMsg?.role === 'assistant') {
69+
logger.info(`[${tracker.requestId}] Received messages to save`, {
70+
messageCount: body.messages?.length,
71+
lastMsgId: lastMsg.id,
72+
lastMsgContentLength: lastMsg.content?.length || 0,
73+
lastMsgContentBlockCount: lastMsg.contentBlocks?.length || 0,
74+
lastMsgContentBlockTypes: lastMsg.contentBlocks?.map((b: any) => b?.type) || [],
75+
})
76+
}
77+
6078
const { chatId, messages, planArtifact, config } = UpdateMessagesSchema.parse(body)
6179

6280
// Verify that the chat belongs to the user

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/copilot-message.tsx

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,28 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
151151
}, [message.content])
152152

153153
// Parse special tags from message content (options, plan)
154-
// Only parse after streaming is complete to avoid affecting streaming smoothness
154+
// Parse during streaming to show options/plan as they stream in
155155
const parsedTags = useMemo(() => {
156-
if (isUser || isStreaming) return null
156+
if (isUser) return null
157+
158+
// Try message.content first
159+
if (message.content) {
160+
const parsed = parseSpecialTags(message.content)
161+
if (parsed.options || parsed.plan) return parsed
162+
}
163+
164+
// During streaming, check content blocks for options/plan
165+
if (isStreaming && message.contentBlocks && message.contentBlocks.length > 0) {
166+
for (const block of message.contentBlocks) {
167+
if (block.type === 'text' && block.content) {
168+
const parsed = parseSpecialTags(block.content)
169+
if (parsed.options || parsed.plan) return parsed
170+
}
171+
}
172+
}
157173

158-
// Only parse when not streaming - options should appear after message completes
159174
return message.content ? parseSpecialTags(message.content) : null
160-
}, [message.content, isUser, isStreaming])
175+
}, [message.content, message.contentBlocks, isUser, isStreaming])
161176

162177
// Get sendMessage from store for continuation actions
163178
const sendMessage = useCopilotStore((s) => s.sendMessage)
@@ -181,14 +196,12 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
181196
if (block.type === 'text') {
182197
const isLastTextBlock =
183198
index === message.contentBlocks!.length - 1 && block.type === 'text'
184-
// During streaming, use raw content for smooth performance
185-
// Only strip special tags after streaming completes
186-
const cleanBlockContent = isStreaming
187-
? block.content.replace(/\n{3,}/g, '\n\n')
188-
: parseSpecialTags(block.content).cleanContent.replace(/\n{3,}/g, '\n\n')
199+
// Always strip special tags from display (they're rendered separately as options/plan)
200+
const parsed = parseSpecialTags(block.content)
201+
const cleanBlockContent = parsed.cleanContent.replace(/\n{3,}/g, '\n\n')
189202

190-
// Skip if no content after stripping tags (only when not streaming)
191-
if (!isStreaming && !cleanBlockContent.trim()) return null
203+
// Skip if no content after stripping tags
204+
if (!cleanBlockContent.trim()) return null
192205

193206
// Use smooth streaming for the last text block if we're streaming
194207
const shouldUseSmoothing = isStreaming && isLastTextBlock
@@ -495,14 +508,14 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
495508
</div>
496509
)}
497510

498-
{/* Options selector when agent presents choices - only shown after streaming completes */}
499-
{!isStreaming && parsedTags?.options && Object.keys(parsedTags.options).length > 0 && (
511+
{/* Options selector when agent presents choices - streams in but disabled until complete */}
512+
{parsedTags?.options && Object.keys(parsedTags.options).length > 0 && (
500513
<OptionsSelector
501514
options={parsedTags.options}
502515
onSelect={handleOptionSelect}
503-
disabled={isSendingMessage}
504-
enableKeyboardNav={isLastMessage}
505-
streaming={false}
516+
disabled={isSendingMessage || isStreaming}
517+
enableKeyboardNav={isLastMessage && !isStreaming && parsedTags.optionsComplete === true}
518+
streaming={isStreaming || !parsedTags.optionsComplete}
506519
/>
507520
)}
508521
</div>

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/tool-call/tool-call.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,13 +1943,23 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
19431943
// Get current mode from store to determine if we should render integration tools
19441944
const mode = useCopilotStore.getState().mode
19451945

1946+
// Check if this is a completed/historical tool call (not pending/executing)
1947+
// Use string comparison to handle both enum values and string values from DB
1948+
const stateStr = String(toolCall.state)
1949+
const isCompletedToolCall =
1950+
stateStr === 'success' ||
1951+
stateStr === 'error' ||
1952+
stateStr === 'rejected' ||
1953+
stateStr === 'aborted'
1954+
19461955
// Allow rendering if:
19471956
// 1. Tool is in CLASS_TOOL_METADATA (client tools), OR
1948-
// 2. We're in build mode (integration tools are executed server-side)
1957+
// 2. We're in build mode (integration tools are executed server-side), OR
1958+
// 3. Tool call is already completed (historical - should always render)
19491959
const isClientTool = !!CLASS_TOOL_METADATA[toolCall.name]
19501960
const isIntegrationToolInBuildMode = mode === 'build' && !isClientTool
19511961

1952-
if (!isClientTool && !isIntegrationToolInBuildMode) {
1962+
if (!isClientTool && !isIntegrationToolInBuildMode && !isCompletedToolCall) {
19531963
return null
19541964
}
19551965
// Check if tool has params table config (meaning it's expandable)

0 commit comments

Comments
 (0)