mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
improvement(chat-deploy): added conversation ID to input
This commit is contained in:
@@ -72,7 +72,7 @@ export async function POST(
|
||||
}
|
||||
|
||||
// Use the already parsed body
|
||||
const { message, password, email } = parsedBody
|
||||
const { message, password, email, conversationId } = parsedBody
|
||||
|
||||
// If this is an authentication request (has password or email but no message),
|
||||
// set auth cookie and return success
|
||||
@@ -105,8 +105,8 @@ export async function POST(
|
||||
}
|
||||
|
||||
try {
|
||||
// Execute the workflow using our helper function
|
||||
const result = await executeWorkflowForChat(deployment.id, message)
|
||||
// Execute workflow with structured input (message + conversationId for context)
|
||||
const result = await executeWorkflowForChat(deployment.id, message, conversationId)
|
||||
|
||||
// If the executor returned a ReadableStream, stream it directly to the client
|
||||
if (result instanceof ReadableStream) {
|
||||
|
||||
@@ -237,13 +237,20 @@ function extractBlockOutput(logs: any[], blockId: string, path?: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a workflow for a chat and extracts the specified output.
|
||||
* This function contains the same logic as the internal chat panel.
|
||||
* Executes a workflow for a chat request and returns the formatted output.
|
||||
*
|
||||
* When workflows reference <start.response.input>, they receive a structured JSON
|
||||
* containing both the message and conversationId for maintaining chat context.
|
||||
*
|
||||
* @param chatId - Chat deployment identifier
|
||||
* @param message - User's chat message
|
||||
* @param conversationId - Optional ID for maintaining conversation context
|
||||
* @returns Workflow execution result formatted for the chat interface
|
||||
*/
|
||||
export async function executeWorkflowForChat(chatId: string, message: string) {
|
||||
export async function executeWorkflowForChat(chatId: string, message: string, conversationId?: string) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
|
||||
logger.debug(`[${requestId}] Executing workflow for chat: ${chatId}`)
|
||||
logger.debug(`[${requestId}] Executing workflow for chat: ${chatId}${conversationId ? `, conversationId: ${conversationId}` : ''}`)
|
||||
|
||||
// Find the chat deployment
|
||||
const deploymentResult = await db
|
||||
@@ -418,7 +425,7 @@ export async function executeWorkflowForChat(chatId: string, message: string) {
|
||||
workflow: serializedWorkflow,
|
||||
currentBlockStates: processedBlockStates,
|
||||
envVarValues: decryptedEnvVars,
|
||||
workflowInput: { input: message },
|
||||
workflowInput: { input: message, conversationId },
|
||||
workflowVariables,
|
||||
contextExtensions: {
|
||||
// Always request streaming – the executor will downgrade gracefully if unsupported
|
||||
@@ -490,10 +497,22 @@ export async function executeWorkflowForChat(chatId: string, message: string) {
|
||||
totalDuration,
|
||||
}
|
||||
|
||||
// Add conversationId to metadata if available
|
||||
if (conversationId) {
|
||||
if (!enrichedResult.metadata) {
|
||||
enrichedResult.metadata = {
|
||||
duration: totalDuration,
|
||||
};
|
||||
}
|
||||
(enrichedResult.metadata as any).conversationId = conversationId;
|
||||
}
|
||||
|
||||
const executionId = uuidv4()
|
||||
await persistExecutionLogs(workflowId, executionId, enrichedResult, 'chat')
|
||||
logger.debug(
|
||||
`[${requestId}] Persisted execution logs for streaming chat with ID: ${executionId}`
|
||||
`[${requestId}] Persisted execution logs for streaming chat with ID: ${executionId}${
|
||||
conversationId ? `, conversationId: ${conversationId}` : ''
|
||||
}`
|
||||
)
|
||||
|
||||
// Update user stats for successful streaming chat execution
|
||||
@@ -562,6 +581,11 @@ export async function executeWorkflowForChat(chatId: string, message: string) {
|
||||
...(result.metadata || {}),
|
||||
source: 'chat',
|
||||
}
|
||||
|
||||
// Add conversationId to metadata if available
|
||||
if (conversationId) {
|
||||
(result as any).metadata.conversationId = conversationId
|
||||
}
|
||||
}
|
||||
|
||||
// Update user stats to increment totalChatExecutions if the execution was successful
|
||||
@@ -606,13 +630,26 @@ export async function executeWorkflowForChat(chatId: string, message: string) {
|
||||
totalDuration,
|
||||
}
|
||||
|
||||
// Add conversation ID to metadata if available
|
||||
if (conversationId) {
|
||||
if (!enrichedResult.metadata) {
|
||||
enrichedResult.metadata = {
|
||||
duration: totalDuration,
|
||||
};
|
||||
}
|
||||
(enrichedResult.metadata as any).conversationId = conversationId;
|
||||
}
|
||||
|
||||
// Generate a unique execution ID for this chat interaction
|
||||
const executionId = uuidv4()
|
||||
|
||||
// Persist the logs with 'chat' trigger type
|
||||
await persistExecutionLogs(workflowId, executionId, enrichedResult, 'chat')
|
||||
|
||||
logger.debug(`[${requestId}] Persisted execution logs for chat with ID: ${executionId}`)
|
||||
logger.debug(
|
||||
`[${requestId}] Persisted execution logs for chat with ID: ${executionId}${
|
||||
conversationId ? `, conversationId: ${conversationId}` : ''
|
||||
}`)
|
||||
} catch (error) {
|
||||
// Don't fail the chat response if logging fails
|
||||
logger.error(`[${requestId}] Failed to persist chat execution logs:`, error)
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import { ArrowUp, Loader2, Lock, Mail } from 'lucide-react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { OTPInputForm } from '@/components/ui/input-otp-form'
|
||||
@@ -99,6 +100,7 @@ export default function ChatClient({ subdomain }: { subdomain: string }) {
|
||||
const messagesContainerRef = useRef<HTMLDivElement>(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const [starCount, setStarCount] = useState('3.4k')
|
||||
const [conversationId, setConversationId] = useState('')
|
||||
|
||||
// Authentication state
|
||||
const [authRequired, setAuthRequired] = useState<'password' | 'email' | null>(null)
|
||||
@@ -163,9 +165,11 @@ export default function ChatClient({ subdomain }: { subdomain: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch chat config on mount
|
||||
// Fetch chat config on mount and generate new conversation ID
|
||||
useEffect(() => {
|
||||
fetchChatConfig()
|
||||
// Generate a new conversation ID whenever the page/chat is refreshed
|
||||
setConversationId(uuidv4())
|
||||
|
||||
// Fetch GitHub stars
|
||||
getFormattedGitHubStars()
|
||||
@@ -380,6 +384,12 @@ export default function ChatClient({ subdomain }: { subdomain: string }) {
|
||||
}
|
||||
|
||||
try {
|
||||
// Send structured payload to maintain chat context
|
||||
const payload = {
|
||||
message: userMessage.content,
|
||||
conversationId,
|
||||
}
|
||||
|
||||
// Use relative URL with credentials
|
||||
const response = await fetch(`/api/chat/${subdomain}`, {
|
||||
method: 'POST',
|
||||
@@ -388,7 +398,7 @@ export default function ChatClient({ subdomain }: { subdomain: string }) {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
body: JSON.stringify({ message: userMessage.content }),
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -714,20 +714,16 @@ export class Executor {
|
||||
})
|
||||
} else {
|
||||
*/
|
||||
// No input format defined or not an array,
|
||||
// Handle API call - prioritize using the input as-is
|
||||
// Handle structured input (like API calls or chat messages)
|
||||
if (this.workflowInput && typeof this.workflowInput === 'object') {
|
||||
// For API calls, extract input from the nested structure if it exists
|
||||
const inputData =
|
||||
this.workflowInput.input !== undefined
|
||||
? this.workflowInput.input // Use the nested input data
|
||||
: this.workflowInput // Fallback to direct input
|
||||
|
||||
// Create starter output with both formats for maximum compatibility
|
||||
// Preserve complete workflowInput structure to maintain JSON format
|
||||
// when referenced through <start.response.input>
|
||||
const starterOutput = {
|
||||
response: {
|
||||
input: inputData,
|
||||
...inputData, // Make fields directly accessible at response level
|
||||
input: this.workflowInput,
|
||||
// Add top-level fields for backward compatibility
|
||||
message: this.workflowInput.input,
|
||||
conversationId: this.workflowInput.conversationId
|
||||
},
|
||||
}
|
||||
|
||||
@@ -737,7 +733,7 @@ export class Executor {
|
||||
executionTime: 0,
|
||||
})
|
||||
} else {
|
||||
// Fallback for other cases
|
||||
// Fallback for primitive input values
|
||||
const starterOutput = {
|
||||
response: {
|
||||
input: this.workflowInput,
|
||||
@@ -754,17 +750,12 @@ export class Executor {
|
||||
} catch (e) {
|
||||
logger.warn('Error processing starter block input format:', e)
|
||||
|
||||
// Fallback to raw input with both paths accessible
|
||||
// Ensure we handle both input formats
|
||||
const inputData =
|
||||
this.workflowInput?.input !== undefined
|
||||
? this.workflowInput.input // Use nested input if available
|
||||
: this.workflowInput // Fallback to direct input
|
||||
|
||||
// Error handler fallback - preserve structure for both direct access and backward compatibility
|
||||
const starterOutput = {
|
||||
response: {
|
||||
input: inputData,
|
||||
...inputData, // Add input fields directly at response level too
|
||||
input: this.workflowInput,
|
||||
message: this.workflowInput?.input,
|
||||
conversationId: this.workflowInput?.conversationId
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -465,6 +465,7 @@ export class InputResolver {
|
||||
}
|
||||
// For all other blocks, stringify objects
|
||||
else {
|
||||
// Preserve full JSON structure for objects (especially for structured inputs with conversationId)
|
||||
formattedValue = JSON.stringify(replacementValue)
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user