Skip to content

Commit 533f765

Browse files
committed
improvement(chat-deploy): added conversation ID to input
1 parent 0af7fb2 commit 533f765

File tree

5 files changed

+72
-33
lines changed

5 files changed

+72
-33
lines changed

apps/sim/app/api/chat/[subdomain]/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export async function POST(
7272
}
7373

7474
// Use the already parsed body
75-
const { message, password, email } = parsedBody
75+
const { message, password, email, conversationId } = parsedBody
7676

7777
// If this is an authentication request (has password or email but no message),
7878
// set auth cookie and return success
@@ -105,8 +105,8 @@ export async function POST(
105105
}
106106

107107
try {
108-
// Execute the workflow using our helper function
109-
const result = await executeWorkflowForChat(deployment.id, message)
108+
// Execute workflow with structured input (message + conversationId for context)
109+
const result = await executeWorkflowForChat(deployment.id, message, conversationId)
110110

111111
// If the executor returned a ReadableStream, stream it directly to the client
112112
if (result instanceof ReadableStream) {

apps/sim/app/api/chat/utils.ts

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,20 @@ function extractBlockOutput(logs: any[], blockId: string, path?: string) {
237237
}
238238

239239
/**
240-
* Executes a workflow for a chat and extracts the specified output.
241-
* This function contains the same logic as the internal chat panel.
240+
* Executes a workflow for a chat request and returns the formatted output.
241+
*
242+
* When workflows reference <start.response.input>, they receive a structured JSON
243+
* containing both the message and conversationId for maintaining chat context.
244+
*
245+
* @param chatId - Chat deployment identifier
246+
* @param message - User's chat message
247+
* @param conversationId - Optional ID for maintaining conversation context
248+
* @returns Workflow execution result formatted for the chat interface
242249
*/
243-
export async function executeWorkflowForChat(chatId: string, message: string) {
250+
export async function executeWorkflowForChat(chatId: string, message: string, conversationId?: string) {
244251
const requestId = crypto.randomUUID().slice(0, 8)
245252

246-
logger.debug(`[${requestId}] Executing workflow for chat: ${chatId}`)
253+
logger.debug(`[${requestId}] Executing workflow for chat: ${chatId}${conversationId ? `, conversationId: ${conversationId}` : ''}`)
247254

248255
// Find the chat deployment
249256
const deploymentResult = await db
@@ -418,7 +425,7 @@ export async function executeWorkflowForChat(chatId: string, message: string) {
418425
workflow: serializedWorkflow,
419426
currentBlockStates: processedBlockStates,
420427
envVarValues: decryptedEnvVars,
421-
workflowInput: { input: message },
428+
workflowInput: { input: message, conversationId },
422429
workflowVariables,
423430
contextExtensions: {
424431
// Always request streaming – the executor will downgrade gracefully if unsupported
@@ -490,10 +497,22 @@ export async function executeWorkflowForChat(chatId: string, message: string) {
490497
totalDuration,
491498
}
492499

500+
// Add conversationId to metadata if available
501+
if (conversationId) {
502+
if (!enrichedResult.metadata) {
503+
enrichedResult.metadata = {
504+
duration: totalDuration,
505+
};
506+
}
507+
(enrichedResult.metadata as any).conversationId = conversationId;
508+
}
509+
493510
const executionId = uuidv4()
494511
await persistExecutionLogs(workflowId, executionId, enrichedResult, 'chat')
495512
logger.debug(
496-
`[${requestId}] Persisted execution logs for streaming chat with ID: ${executionId}`
513+
`[${requestId}] Persisted execution logs for streaming chat with ID: ${executionId}${
514+
conversationId ? `, conversationId: ${conversationId}` : ''
515+
}`
497516
)
498517

499518
// Update user stats for successful streaming chat execution
@@ -562,6 +581,11 @@ export async function executeWorkflowForChat(chatId: string, message: string) {
562581
...(result.metadata || {}),
563582
source: 'chat',
564583
}
584+
585+
// Add conversationId to metadata if available
586+
if (conversationId) {
587+
(result as any).metadata.conversationId = conversationId
588+
}
565589
}
566590

567591
// Update user stats to increment totalChatExecutions if the execution was successful
@@ -606,13 +630,26 @@ export async function executeWorkflowForChat(chatId: string, message: string) {
606630
totalDuration,
607631
}
608632

633+
// Add conversation ID to metadata if available
634+
if (conversationId) {
635+
if (!enrichedResult.metadata) {
636+
enrichedResult.metadata = {
637+
duration: totalDuration,
638+
};
639+
}
640+
(enrichedResult.metadata as any).conversationId = conversationId;
641+
}
642+
609643
// Generate a unique execution ID for this chat interaction
610644
const executionId = uuidv4()
611645

612646
// Persist the logs with 'chat' trigger type
613647
await persistExecutionLogs(workflowId, executionId, enrichedResult, 'chat')
614648

615-
logger.debug(`[${requestId}] Persisted execution logs for chat with ID: ${executionId}`)
649+
logger.debug(
650+
`[${requestId}] Persisted execution logs for chat with ID: ${executionId}${
651+
conversationId ? `, conversationId: ${conversationId}` : ''
652+
}`)
616653
} catch (error) {
617654
// Don't fail the chat response if logging fails
618655
logger.error(`[${requestId}] Failed to persist chat execution logs:`, error)

apps/sim/app/chat/[subdomain]/components/chat-client.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
useState,
1111
} from 'react'
1212
import { ArrowUp, Loader2, Lock, Mail } from 'lucide-react'
13+
import { v4 as uuidv4 } from 'uuid'
1314
import { Button } from '@/components/ui/button'
1415
import { Input } from '@/components/ui/input'
1516
import { OTPInputForm } from '@/components/ui/input-otp-form'
@@ -99,6 +100,7 @@ export default function ChatClient({ subdomain }: { subdomain: string }) {
99100
const messagesContainerRef = useRef<HTMLDivElement>(null)
100101
const inputRef = useRef<HTMLInputElement>(null)
101102
const [starCount, setStarCount] = useState('3.4k')
103+
const [conversationId, setConversationId] = useState('')
102104

103105
// Authentication state
104106
const [authRequired, setAuthRequired] = useState<'password' | 'email' | null>(null)
@@ -163,9 +165,11 @@ export default function ChatClient({ subdomain }: { subdomain: string }) {
163165
}
164166
}
165167

166-
// Fetch chat config on mount
168+
// Fetch chat config on mount and generate new conversation ID
167169
useEffect(() => {
168170
fetchChatConfig()
171+
// Generate a new conversation ID whenever the page/chat is refreshed
172+
setConversationId(uuidv4())
169173

170174
// Fetch GitHub stars
171175
getFormattedGitHubStars()
@@ -380,6 +384,12 @@ export default function ChatClient({ subdomain }: { subdomain: string }) {
380384
}
381385

382386
try {
387+
// Send structured payload to maintain chat context
388+
const payload = {
389+
message: userMessage.content,
390+
conversationId,
391+
}
392+
383393
// Use relative URL with credentials
384394
const response = await fetch(`/api/chat/${subdomain}`, {
385395
method: 'POST',
@@ -388,7 +398,7 @@ export default function ChatClient({ subdomain }: { subdomain: string }) {
388398
'Content-Type': 'application/json',
389399
'X-Requested-With': 'XMLHttpRequest',
390400
},
391-
body: JSON.stringify({ message: userMessage.content }),
401+
body: JSON.stringify(payload),
392402
})
393403

394404
if (!response.ok) {

apps/sim/executor/index.ts

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -714,20 +714,16 @@ export class Executor {
714714
})
715715
} else {
716716
*/
717-
// No input format defined or not an array,
718-
// Handle API call - prioritize using the input as-is
717+
// Handle structured input (like API calls or chat messages)
719718
if (this.workflowInput && typeof this.workflowInput === 'object') {
720-
// For API calls, extract input from the nested structure if it exists
721-
const inputData =
722-
this.workflowInput.input !== undefined
723-
? this.workflowInput.input // Use the nested input data
724-
: this.workflowInput // Fallback to direct input
725-
726-
// Create starter output with both formats for maximum compatibility
719+
// Preserve complete workflowInput structure to maintain JSON format
720+
// when referenced through <start.response.input>
727721
const starterOutput = {
728722
response: {
729-
input: inputData,
730-
...inputData, // Make fields directly accessible at response level
723+
input: this.workflowInput,
724+
// Add top-level fields for backward compatibility
725+
message: this.workflowInput.input,
726+
conversationId: this.workflowInput.conversationId
731727
},
732728
}
733729

@@ -737,7 +733,7 @@ export class Executor {
737733
executionTime: 0,
738734
})
739735
} else {
740-
// Fallback for other cases
736+
// Fallback for primitive input values
741737
const starterOutput = {
742738
response: {
743739
input: this.workflowInput,
@@ -754,17 +750,12 @@ export class Executor {
754750
} catch (e) {
755751
logger.warn('Error processing starter block input format:', e)
756752

757-
// Fallback to raw input with both paths accessible
758-
// Ensure we handle both input formats
759-
const inputData =
760-
this.workflowInput?.input !== undefined
761-
? this.workflowInput.input // Use nested input if available
762-
: this.workflowInput // Fallback to direct input
763-
753+
// Error handler fallback - preserve structure for both direct access and backward compatibility
764754
const starterOutput = {
765755
response: {
766-
input: inputData,
767-
...inputData, // Add input fields directly at response level too
756+
input: this.workflowInput,
757+
message: this.workflowInput?.input,
758+
conversationId: this.workflowInput?.conversationId
768759
},
769760
}
770761

apps/sim/executor/resolver.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ export class InputResolver {
465465
}
466466
// For all other blocks, stringify objects
467467
else {
468+
// Preserve full JSON structure for objects (especially for structured inputs with conversationId)
468469
formattedValue = JSON.stringify(replacementValue)
469470
}
470471
} else {

0 commit comments

Comments
 (0)