mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-19 03:48:02 -05:00
Compare commits
28 Commits
fix/socket
...
v0.5.61
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af82820a28 | ||
|
|
4372841797 | ||
|
|
5e8c843241 | ||
|
|
7bf3d73ee6 | ||
|
|
7ffc11a738 | ||
|
|
be578e2ed7 | ||
|
|
f415e5edc4 | ||
|
|
13a6e6c3fa | ||
|
|
f5ab7f21ae | ||
|
|
bfb6fffe38 | ||
|
|
4fbec0a43f | ||
|
|
585f5e365b | ||
|
|
3792bdd252 | ||
|
|
eb5d1f3e5b | ||
|
|
54ab82c8dd | ||
|
|
f895bf469b | ||
|
|
dd3209af06 | ||
|
|
b6ba3b50a7 | ||
|
|
b304233062 | ||
|
|
57e4b49bd6 | ||
|
|
e12dd204ed | ||
|
|
3d9d9cbc54 | ||
|
|
0f4ec962ad | ||
|
|
4827866f9a | ||
|
|
3e697d9ed9 | ||
|
|
4431a1a484 | ||
|
|
4d1a9a3f22 | ||
|
|
eb07a080fb |
@@ -9,12 +9,12 @@
|
||||
<p align="center">
|
||||
<a href="https://sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/sim.ai-6F3DFA" alt="Sim.ai"></a>
|
||||
<a href="https://discord.gg/Hr4UWYEcTT" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
|
||||
<a href="https://x.com/simdotai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/twitter/follow/simdotai?style=social" alt="Twitter"></a>
|
||||
<a href="https://docs.sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Docs-6F3DFA.svg" alt="Documentation"></a>
|
||||
<a href="https://x.com/simdotai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/twitter/follow/simstudioai?style=social" alt="Twitter"></a>
|
||||
<a href="https://docs.sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Docs-6F3DFA.svg" alt="Documentation"></a> <a href="https://deepwiki.com/simstudioai/sim" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/DeepWiki-1E90FF.svg" alt="DeepWiki"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://deepwiki.com/simstudioai/sim" target="_blank" rel="noopener noreferrer"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a> <a href="https://cursor.com/link/prompt?text=Help%20me%20set%20up%20Sim%20Studio%20locally.%20Follow%20these%20steps%3A%0A%0A1.%20First%2C%20verify%20Docker%20is%20installed%20and%20running%3A%0A%20%20%20docker%20--version%0A%20%20%20docker%20info%0A%0A2.%20Clone%20the%20repository%3A%0A%20%20%20git%20clone%20https%3A%2F%2Fgithub.com%2Fsimstudioai%2Fsim.git%0A%20%20%20cd%20sim%0A%0A3.%20Start%20the%20services%20with%20Docker%20Compose%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.prod.yml%20up%20-d%0A%0A4.%20Wait%20for%20all%20containers%20to%20be%20healthy%20(this%20may%20take%201-2%20minutes)%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.prod.yml%20ps%0A%0A5.%20Verify%20the%20app%20is%20accessible%20at%20http%3A%2F%2Flocalhost%3A3000%0A%0AIf%20there%20are%20any%20errors%2C%20help%20me%20troubleshoot%20them.%20Common%20issues%3A%0A-%20Port%203000%2C%203002%2C%20or%205432%20already%20in%20use%0A-%20Docker%20not%20running%0A-%20Insufficient%20memory%20(needs%2012GB%2B%20RAM)%0A%0AFor%20local%20AI%20models%20with%20Ollama%2C%20use%20this%20instead%20of%20step%203%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.ollama.yml%20--profile%20setup%20up%20-d"><img src="https://img.shields.io/badge/Set%20Up%20with-Cursor-000000?logo=cursor&logoColor=white" alt="Set Up with Cursor"></a>
|
||||
<a href="https://cursor.com/link/prompt?text=Help%20me%20set%20up%20Sim%20Studio%20locally.%20Follow%20these%20steps%3A%0A%0A1.%20First%2C%20verify%20Docker%20is%20installed%20and%20running%3A%0A%20%20%20docker%20--version%0A%20%20%20docker%20info%0A%0A2.%20Clone%20the%20repository%3A%0A%20%20%20git%20clone%20https%3A%2F%2Fgithub.com%2Fsimstudioai%2Fsim.git%0A%20%20%20cd%20sim%0A%0A3.%20Start%20the%20services%20with%20Docker%20Compose%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.prod.yml%20up%20-d%0A%0A4.%20Wait%20for%20all%20containers%20to%20be%20healthy%20(this%20may%20take%201-2%20minutes)%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.prod.yml%20ps%0A%0A5.%20Verify%20the%20app%20is%20accessible%20at%20http%3A%2F%2Flocalhost%3A3000%0A%0AIf%20there%20are%20any%20errors%2C%20help%20me%20troubleshoot%20them.%20Common%20issues%3A%0A-%20Port%203000%2C%203002%2C%20or%205432%20already%20in%20use%0A-%20Docker%20not%20running%0A-%20Insufficient%20memory%20(needs%2012GB%2B%20RAM)%0A%0AFor%20local%20AI%20models%20with%20Ollama%2C%20use%20this%20instead%20of%20step%203%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.ollama.yml%20--profile%20setup%20up%20-d"><img src="https://img.shields.io/badge/Set%20Up%20with-Cursor-000000?logo=cursor&logoColor=white" alt="Set Up with Cursor"></a>
|
||||
</p>
|
||||
|
||||
### Build Workflows with Ease
|
||||
|
||||
@@ -2499,9 +2499,7 @@ export const editWorkflowServerTool: BaseServerTool<EditWorkflowParams, any> = {
|
||||
async execute(params: EditWorkflowParams, context?: { userId: string }): Promise<any> {
|
||||
const logger = createLogger('EditWorkflowServerTool')
|
||||
const { operations, workflowId, currentUserWorkflow } = params
|
||||
if (!Array.isArray(operations) || operations.length === 0) {
|
||||
throw new Error('operations are required and must be an array')
|
||||
}
|
||||
if (!operations || operations.length === 0) throw new Error('operations are required')
|
||||
if (!workflowId) throw new Error('workflowId is required')
|
||||
|
||||
logger.info('Executing edit_workflow', {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import crypto from 'crypto'
|
||||
import {
|
||||
db,
|
||||
webhook,
|
||||
workflow,
|
||||
workflowBlocks,
|
||||
workflowDeploymentVersion,
|
||||
@@ -21,6 +22,7 @@ import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/w
|
||||
const logger = createLogger('WorkflowDBHelpers')
|
||||
|
||||
export type WorkflowDeploymentVersion = InferSelectModel<typeof workflowDeploymentVersion>
|
||||
type WebhookRecord = InferSelectModel<typeof webhook>
|
||||
type SubflowInsert = InferInsertModel<typeof workflowSubflows>
|
||||
|
||||
export interface WorkflowDeploymentVersionResponse {
|
||||
@@ -335,6 +337,18 @@ export async function saveWorkflowToNormalizedTables(
|
||||
|
||||
// Start a transaction
|
||||
await db.transaction(async (tx) => {
|
||||
// Snapshot existing webhooks before deletion to preserve them through the cycle
|
||||
let existingWebhooks: WebhookRecord[] = []
|
||||
try {
|
||||
existingWebhooks = await tx.select().from(webhook).where(eq(webhook.workflowId, workflowId))
|
||||
} catch (webhookError) {
|
||||
// Webhook table might not be available in test environments
|
||||
logger.debug('Could not load webhooks before save, skipping preservation', {
|
||||
error: webhookError instanceof Error ? webhookError.message : String(webhookError),
|
||||
})
|
||||
}
|
||||
|
||||
// Clear existing data for this workflow
|
||||
await Promise.all([
|
||||
tx.delete(workflowBlocks).where(eq(workflowBlocks.workflowId, workflowId)),
|
||||
tx.delete(workflowEdges).where(eq(workflowEdges.workflowId, workflowId)),
|
||||
@@ -405,6 +419,42 @@ export async function saveWorkflowToNormalizedTables(
|
||||
if (subflowInserts.length > 0) {
|
||||
await tx.insert(workflowSubflows).values(subflowInserts)
|
||||
}
|
||||
|
||||
// Re-insert preserved webhooks if any exist and their blocks still exist
|
||||
if (existingWebhooks.length > 0) {
|
||||
try {
|
||||
const webhookInserts = existingWebhooks
|
||||
.filter((wh) => !!state.blocks?.[wh.blockId ?? ''])
|
||||
.map((wh) => ({
|
||||
id: wh.id,
|
||||
workflowId: wh.workflowId,
|
||||
blockId: wh.blockId,
|
||||
path: wh.path,
|
||||
provider: wh.provider,
|
||||
providerConfig: wh.providerConfig,
|
||||
credentialSetId: wh.credentialSetId,
|
||||
isActive: wh.isActive,
|
||||
createdAt: wh.createdAt,
|
||||
updatedAt: new Date(),
|
||||
}))
|
||||
|
||||
if (webhookInserts.length > 0) {
|
||||
await tx.insert(webhook).values(webhookInserts)
|
||||
logger.debug(`Preserved ${webhookInserts.length} webhook(s) through workflow save`, {
|
||||
workflowId,
|
||||
})
|
||||
}
|
||||
} catch (webhookInsertError) {
|
||||
// Webhook preservation is optional - don't fail the entire save if it errors
|
||||
logger.warn('Could not preserve webhooks during save', {
|
||||
error:
|
||||
webhookInsertError instanceof Error
|
||||
? webhookInsertError.message
|
||||
: String(webhookInsertError),
|
||||
workflowId,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return { success: true }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as schema from '@sim/db'
|
||||
import { webhook, workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@sim/db'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { InferSelectModel } from 'drizzle-orm'
|
||||
import { and, eq, inArray, or, sql } from 'drizzle-orm'
|
||||
import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
import postgres from 'postgres'
|
||||
@@ -1174,6 +1175,14 @@ async function handleWorkflowOperationTx(
|
||||
parallelCount: Object.keys(parallels || {}).length,
|
||||
})
|
||||
|
||||
// Snapshot existing webhooks before deletion to preserve them through the cycle
|
||||
// (workflowBlocks has CASCADE DELETE to webhook table)
|
||||
const existingWebhooks = await tx
|
||||
.select()
|
||||
.from(webhook)
|
||||
.where(eq(webhook.workflowId, workflowId))
|
||||
|
||||
// Delete all existing blocks (this will cascade delete edges and webhooks via ON DELETE CASCADE)
|
||||
await tx.delete(workflowBlocks).where(eq(workflowBlocks.workflowId, workflowId))
|
||||
|
||||
// Delete all existing subflows
|
||||
@@ -1239,6 +1248,32 @@ async function handleWorkflowOperationTx(
|
||||
await tx.insert(workflowSubflows).values(parallelValues)
|
||||
}
|
||||
|
||||
// Re-insert preserved webhooks if any exist and their blocks still exist
|
||||
type WebhookRecord = InferSelectModel<typeof webhook>
|
||||
if (existingWebhooks.length > 0) {
|
||||
const webhookInserts = existingWebhooks
|
||||
.filter((wh: WebhookRecord) => !!blocks?.[wh.blockId ?? ''])
|
||||
.map((wh: WebhookRecord) => ({
|
||||
id: wh.id,
|
||||
workflowId: wh.workflowId,
|
||||
blockId: wh.blockId,
|
||||
path: wh.path,
|
||||
provider: wh.provider,
|
||||
providerConfig: wh.providerConfig,
|
||||
credentialSetId: wh.credentialSetId,
|
||||
isActive: wh.isActive,
|
||||
createdAt: wh.createdAt,
|
||||
updatedAt: new Date(),
|
||||
}))
|
||||
|
||||
if (webhookInserts.length > 0) {
|
||||
await tx.insert(webhook).values(webhookInserts)
|
||||
logger.debug(`Preserved ${webhookInserts.length} webhook(s) through state replacement`, {
|
||||
workflowId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Successfully replaced workflow state for ${workflowId}`)
|
||||
break
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user